4 # Copyright (C) 2006, 2007, 2008 Google Inc.
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.
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.
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
22 """Module implementing the master-side code."""
24 # pylint: disable-msg=W0201
26 # W0201 since most LU attributes are defined in CheckPrereq or similar
38 from ganeti import ssh
39 from ganeti import utils
40 from ganeti import errors
41 from ganeti import hypervisor
42 from ganeti import locking
43 from ganeti import constants
44 from ganeti import objects
45 from ganeti import serializer
46 from ganeti import ssconf
47 from ganeti import uidpool
50 class LogicalUnit(object):
51 """Logical Unit base class.
53 Subclasses must follow these rules:
54 - implement ExpandNames
55 - implement CheckPrereq (except when tasklets are used)
56 - implement Exec (except when tasklets are used)
57 - implement BuildHooksEnv
58 - redefine HPATH and HTYPE
59 - optionally redefine their run requirements:
60 REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
62 Note that all commands require root permissions.
64 @ivar dry_run_result: the value (if any) that will be returned to the caller
65 in dry-run mode (signalled by opcode dry_run parameter)
73 def __init__(self, processor, op, context, rpc):
74 """Constructor for LogicalUnit.
76 This needs to be overridden in derived classes in order to check op
82 self.cfg = context.cfg
83 self.context = context
85 # Dicts used to declare locking needs to mcpu
86 self.needed_locks = None
87 self.acquired_locks = {}
88 self.share_locks = dict.fromkeys(locking.LEVELS, 0)
90 self.remove_locks = {}
91 # Used to force good behavior when calling helper functions
92 self.recalculate_locks = {}
95 self.LogWarning = processor.LogWarning # pylint: disable-msg=C0103
96 self.LogInfo = processor.LogInfo # pylint: disable-msg=C0103
97 self.LogStep = processor.LogStep # pylint: disable-msg=C0103
99 self.dry_run_result = None
100 # support for generic debug attribute
101 if (not hasattr(self.op, "debug_level") or
102 not isinstance(self.op.debug_level, int)):
103 self.op.debug_level = 0
108 for attr_name in self._OP_REQP:
109 attr_val = getattr(op, attr_name, None)
111 raise errors.OpPrereqError("Required parameter '%s' missing" %
112 attr_name, errors.ECODE_INVAL)
114 self.CheckArguments()
117 """Returns the SshRunner object
121 self.__ssh = ssh.SshRunner(self.cfg.GetClusterName())
124 ssh = property(fget=__GetSSH)
126 def CheckArguments(self):
127 """Check syntactic validity for the opcode arguments.
129 This method is for doing a simple syntactic check and ensure
130 validity of opcode parameters, without any cluster-related
131 checks. While the same can be accomplished in ExpandNames and/or
132 CheckPrereq, doing these separate is better because:
134 - ExpandNames is left as as purely a lock-related function
135 - CheckPrereq is run after we have acquired locks (and possible
138 The function is allowed to change the self.op attribute so that
139 later methods can no longer worry about missing parameters.
144 def ExpandNames(self):
145 """Expand names for this LU.
147 This method is called before starting to execute the opcode, and it should
148 update all the parameters of the opcode to their canonical form (e.g. a
149 short node name must be fully expanded after this method has successfully
150 completed). This way locking, hooks, logging, ecc. can work correctly.
152 LUs which implement this method must also populate the self.needed_locks
153 member, as a dict with lock levels as keys, and a list of needed lock names
156 - use an empty dict if you don't need any lock
157 - if you don't need any lock at a particular level omit that level
158 - don't put anything for the BGL level
159 - if you want all locks at a level use locking.ALL_SET as a value
161 If you need to share locks (rather than acquire them exclusively) at one
162 level you can modify self.share_locks, setting a true value (usually 1) for
163 that level. By default locks are not shared.
165 This function can also define a list of tasklets, which then will be
166 executed in order instead of the usual LU-level CheckPrereq and Exec
167 functions, if those are not defined by the LU.
171 # Acquire all nodes and one instance
172 self.needed_locks = {
173 locking.LEVEL_NODE: locking.ALL_SET,
174 locking.LEVEL_INSTANCE: ['instance1.example.tld'],
176 # Acquire just two nodes
177 self.needed_locks = {
178 locking.LEVEL_NODE: ['node1.example.tld', 'node2.example.tld'],
181 self.needed_locks = {} # No, you can't leave it to the default value None
184 # The implementation of this method is mandatory only if the new LU is
185 # concurrent, so that old LUs don't need to be changed all at the same
188 self.needed_locks = {} # Exclusive LUs don't need locks.
190 raise NotImplementedError
192 def DeclareLocks(self, level):
193 """Declare LU locking needs for a level
195 While most LUs can just declare their locking needs at ExpandNames time,
196 sometimes there's the need to calculate some locks after having acquired
197 the ones before. This function is called just before acquiring locks at a
198 particular level, but after acquiring the ones at lower levels, and permits
199 such calculations. It can be used to modify self.needed_locks, and by
200 default it does nothing.
202 This function is only called if you have something already set in
203 self.needed_locks for the level.
205 @param level: Locking level which is going to be locked
206 @type level: member of ganeti.locking.LEVELS
210 def CheckPrereq(self):
211 """Check prerequisites for this LU.
213 This method should check that the prerequisites for the execution
214 of this LU are fulfilled. It can do internode communication, but
215 it should be idempotent - no cluster or system changes are
218 The method should raise errors.OpPrereqError in case something is
219 not fulfilled. Its return value is ignored.
221 This method should also update all the parameters of the opcode to
222 their canonical form if it hasn't been done by ExpandNames before.
225 if self.tasklets is not None:
226 for (idx, tl) in enumerate(self.tasklets):
227 logging.debug("Checking prerequisites for tasklet %s/%s",
228 idx + 1, len(self.tasklets))
231 raise NotImplementedError
233 def Exec(self, feedback_fn):
236 This method should implement the actual work. It should raise
237 errors.OpExecError for failures that are somewhat dealt with in
241 if self.tasklets is not None:
242 for (idx, tl) in enumerate(self.tasklets):
243 logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
246 raise NotImplementedError
248 def BuildHooksEnv(self):
249 """Build hooks environment for this LU.
251 This method should return a three-node tuple consisting of: a dict
252 containing the environment that will be used for running the
253 specific hook for this LU, a list of node names on which the hook
254 should run before the execution, and a list of node names on which
255 the hook should run after the execution.
257 The keys of the dict must not have 'GANETI_' prefixed as this will
258 be handled in the hooks runner. Also note additional keys will be
259 added by the hooks runner. If the LU doesn't define any
260 environment, an empty dict (and not None) should be returned.
262 No nodes should be returned as an empty list (and not None).
264 Note that if the HPATH for a LU class is None, this function will
268 raise NotImplementedError
270 def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
271 """Notify the LU about the results of its hooks.
273 This method is called every time a hooks phase is executed, and notifies
274 the Logical Unit about the hooks' result. The LU can then use it to alter
275 its result based on the hooks. By default the method does nothing and the
276 previous result is passed back unchanged but any LU can define it if it
277 wants to use the local cluster hook-scripts somehow.
279 @param phase: one of L{constants.HOOKS_PHASE_POST} or
280 L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
281 @param hook_results: the results of the multi-node hooks rpc call
282 @param feedback_fn: function used send feedback back to the caller
283 @param lu_result: the previous Exec result this LU had, or None
285 @return: the new Exec result, based on the previous result
289 # API must be kept, thus we ignore the unused argument and could
290 # be a function warnings
291 # pylint: disable-msg=W0613,R0201
294 def _ExpandAndLockInstance(self):
295 """Helper function to expand and lock an instance.
297 Many LUs that work on an instance take its name in self.op.instance_name
298 and need to expand it and then declare the expanded name for locking. This
299 function does it, and then updates self.op.instance_name to the expanded
300 name. It also initializes needed_locks as a dict, if this hasn't been done
304 if self.needed_locks is None:
305 self.needed_locks = {}
307 assert locking.LEVEL_INSTANCE not in self.needed_locks, \
308 "_ExpandAndLockInstance called with instance-level locks set"
309 self.op.instance_name = _ExpandInstanceName(self.cfg,
310 self.op.instance_name)
311 self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
313 def _LockInstancesNodes(self, primary_only=False):
314 """Helper function to declare instances' nodes for locking.
316 This function should be called after locking one or more instances to lock
317 their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
318 with all primary or secondary nodes for instances already locked and
319 present in self.needed_locks[locking.LEVEL_INSTANCE].
321 It should be called from DeclareLocks, and for safety only works if
322 self.recalculate_locks[locking.LEVEL_NODE] is set.
324 In the future it may grow parameters to just lock some instance's nodes, or
325 to just lock primaries or secondary nodes, if needed.
327 If should be called in DeclareLocks in a way similar to::
329 if level == locking.LEVEL_NODE:
330 self._LockInstancesNodes()
332 @type primary_only: boolean
333 @param primary_only: only lock primary nodes of locked instances
336 assert locking.LEVEL_NODE in self.recalculate_locks, \
337 "_LockInstancesNodes helper function called with no nodes to recalculate"
339 # TODO: check if we're really been called with the instance locks held
341 # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
342 # future we might want to have different behaviors depending on the value
343 # of self.recalculate_locks[locking.LEVEL_NODE]
345 for instance_name in self.acquired_locks[locking.LEVEL_INSTANCE]:
346 instance = self.context.cfg.GetInstanceInfo(instance_name)
347 wanted_nodes.append(instance.primary_node)
349 wanted_nodes.extend(instance.secondary_nodes)
351 if self.recalculate_locks[locking.LEVEL_NODE] == constants.LOCKS_REPLACE:
352 self.needed_locks[locking.LEVEL_NODE] = wanted_nodes
353 elif self.recalculate_locks[locking.LEVEL_NODE] == constants.LOCKS_APPEND:
354 self.needed_locks[locking.LEVEL_NODE].extend(wanted_nodes)
356 del self.recalculate_locks[locking.LEVEL_NODE]
359 class NoHooksLU(LogicalUnit): # pylint: disable-msg=W0223
360 """Simple LU which runs no hooks.
362 This LU is intended as a parent for other LogicalUnits which will
363 run no hooks, in order to reduce duplicate code.
369 def BuildHooksEnv(self):
370 """Empty BuildHooksEnv for NoHooksLu.
372 This just raises an error.
375 assert False, "BuildHooksEnv called for NoHooksLUs"
379 """Tasklet base class.
381 Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
382 they can mix legacy code with tasklets. Locking needs to be done in the LU,
383 tasklets know nothing about locks.
385 Subclasses must follow these rules:
386 - Implement CheckPrereq
390 def __init__(self, lu):
397 def CheckPrereq(self):
398 """Check prerequisites for this tasklets.
400 This method should check whether the prerequisites for the execution of
401 this tasklet are fulfilled. It can do internode communication, but it
402 should be idempotent - no cluster or system changes are allowed.
404 The method should raise errors.OpPrereqError in case something is not
405 fulfilled. Its return value is ignored.
407 This method should also update all parameters to their canonical form if it
408 hasn't been done before.
411 raise NotImplementedError
413 def Exec(self, feedback_fn):
414 """Execute the tasklet.
416 This method should implement the actual work. It should raise
417 errors.OpExecError for failures that are somewhat dealt with in code, or
421 raise NotImplementedError
424 def _GetWantedNodes(lu, nodes):
425 """Returns list of checked and expanded node names.
427 @type lu: L{LogicalUnit}
428 @param lu: the logical unit on whose behalf we execute
430 @param nodes: list of node names or None for all nodes
432 @return: the list of nodes, sorted
433 @raise errors.ProgrammerError: if the nodes parameter is wrong type
436 if not isinstance(nodes, list):
437 raise errors.OpPrereqError("Invalid argument type 'nodes'",
441 raise errors.ProgrammerError("_GetWantedNodes should only be called with a"
442 " non-empty list of nodes whose name is to be expanded.")
444 wanted = [_ExpandNodeName(lu.cfg, name) for name in nodes]
445 return utils.NiceSort(wanted)
448 def _GetWantedInstances(lu, instances):
449 """Returns list of checked and expanded instance names.
451 @type lu: L{LogicalUnit}
452 @param lu: the logical unit on whose behalf we execute
453 @type instances: list
454 @param instances: list of instance names or None for all instances
456 @return: the list of instances, sorted
457 @raise errors.OpPrereqError: if the instances parameter is wrong type
458 @raise errors.OpPrereqError: if any of the passed instances is not found
461 if not isinstance(instances, list):
462 raise errors.OpPrereqError("Invalid argument type 'instances'",
466 wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
468 wanted = utils.NiceSort(lu.cfg.GetInstanceList())
472 def _CheckOutputFields(static, dynamic, selected):
473 """Checks whether all selected fields are valid.
475 @type static: L{utils.FieldSet}
476 @param static: static fields set
477 @type dynamic: L{utils.FieldSet}
478 @param dynamic: dynamic fields set
485 delta = f.NonMatching(selected)
487 raise errors.OpPrereqError("Unknown output fields selected: %s"
488 % ",".join(delta), errors.ECODE_INVAL)
491 def _CheckBooleanOpField(op, name):
492 """Validates boolean opcode parameters.
494 This will ensure that an opcode parameter is either a boolean value,
495 or None (but that it always exists).
498 val = getattr(op, name, None)
499 if not (val is None or isinstance(val, bool)):
500 raise errors.OpPrereqError("Invalid boolean parameter '%s' (%s)" %
501 (name, str(val)), errors.ECODE_INVAL)
502 setattr(op, name, val)
505 def _CheckGlobalHvParams(params):
506 """Validates that given hypervisor params are not global ones.
508 This will ensure that instances don't get customised versions of
512 used_globals = constants.HVC_GLOBALS.intersection(params)
514 msg = ("The following hypervisor parameters are global and cannot"
515 " be customized at instance level, please modify them at"
516 " cluster level: %s" % utils.CommaJoin(used_globals))
517 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
520 def _CheckNodeOnline(lu, node):
521 """Ensure that a given node is online.
523 @param lu: the LU on behalf of which we make the check
524 @param node: the node to check
525 @raise errors.OpPrereqError: if the node is offline
528 if lu.cfg.GetNodeInfo(node).offline:
529 raise errors.OpPrereqError("Can't use offline node %s" % node,
533 def _CheckNodeNotDrained(lu, node):
534 """Ensure that a given node is not drained.
536 @param lu: the LU on behalf of which we make the check
537 @param node: the node to check
538 @raise errors.OpPrereqError: if the node is drained
541 if lu.cfg.GetNodeInfo(node).drained:
542 raise errors.OpPrereqError("Can't use drained node %s" % node,
546 def _CheckNodeHasOS(lu, node, os_name, force_variant):
547 """Ensure that a node supports a given OS.
549 @param lu: the LU on behalf of which we make the check
550 @param node: the node to check
551 @param os_name: the OS to query about
552 @param force_variant: whether to ignore variant errors
553 @raise errors.OpPrereqError: if the node is not supporting the OS
556 result = lu.rpc.call_os_get(node, os_name)
557 result.Raise("OS '%s' not in supported OS list for node %s" %
559 prereq=True, ecode=errors.ECODE_INVAL)
560 if not force_variant:
561 _CheckOSVariant(result.payload, os_name)
564 def _RequireFileStorage():
565 """Checks that file storage is enabled.
567 @raise errors.OpPrereqError: when file storage is disabled
570 if not constants.ENABLE_FILE_STORAGE:
571 raise errors.OpPrereqError("File storage disabled at configure time",
575 def _CheckDiskTemplate(template):
576 """Ensure a given disk template is valid.
579 if template not in constants.DISK_TEMPLATES:
580 msg = ("Invalid disk template name '%s', valid templates are: %s" %
581 (template, utils.CommaJoin(constants.DISK_TEMPLATES)))
582 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
583 if template == constants.DT_FILE:
584 _RequireFileStorage()
587 def _CheckStorageType(storage_type):
588 """Ensure a given storage type is valid.
591 if storage_type not in constants.VALID_STORAGE_TYPES:
592 raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
594 if storage_type == constants.ST_FILE:
595 _RequireFileStorage()
599 def _CheckInstanceDown(lu, instance, reason):
600 """Ensure that an instance is not running."""
601 if instance.admin_up:
602 raise errors.OpPrereqError("Instance %s is marked to be up, %s" %
603 (instance.name, reason), errors.ECODE_STATE)
605 pnode = instance.primary_node
606 ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
607 ins_l.Raise("Can't contact node %s for instance information" % pnode,
608 prereq=True, ecode=errors.ECODE_ENVIRON)
610 if instance.name in ins_l.payload:
611 raise errors.OpPrereqError("Instance %s is running, %s" %
612 (instance.name, reason), errors.ECODE_STATE)
615 def _ExpandItemName(fn, name, kind):
616 """Expand an item name.
618 @param fn: the function to use for expansion
619 @param name: requested item name
620 @param kind: text description ('Node' or 'Instance')
621 @return: the resolved (full) name
622 @raise errors.OpPrereqError: if the item is not found
626 if full_name is None:
627 raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
632 def _ExpandNodeName(cfg, name):
633 """Wrapper over L{_ExpandItemName} for nodes."""
634 return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
637 def _ExpandInstanceName(cfg, name):
638 """Wrapper over L{_ExpandItemName} for instance."""
639 return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
642 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
643 memory, vcpus, nics, disk_template, disks,
644 bep, hvp, hypervisor_name):
645 """Builds instance related env variables for hooks
647 This builds the hook environment from individual variables.
650 @param name: the name of the instance
651 @type primary_node: string
652 @param primary_node: the name of the instance's primary node
653 @type secondary_nodes: list
654 @param secondary_nodes: list of secondary nodes as strings
655 @type os_type: string
656 @param os_type: the name of the instance's OS
657 @type status: boolean
658 @param status: the should_run status of the instance
660 @param memory: the memory size of the instance
662 @param vcpus: the count of VCPUs the instance has
664 @param nics: list of tuples (ip, mac, mode, link) representing
665 the NICs the instance has
666 @type disk_template: string
667 @param disk_template: the disk template of the instance
669 @param disks: the list of (size, mode) pairs
671 @param bep: the backend parameters for the instance
673 @param hvp: the hypervisor parameters for the instance
674 @type hypervisor_name: string
675 @param hypervisor_name: the hypervisor for the instance
677 @return: the hook environment for this instance
686 "INSTANCE_NAME": name,
687 "INSTANCE_PRIMARY": primary_node,
688 "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
689 "INSTANCE_OS_TYPE": os_type,
690 "INSTANCE_STATUS": str_status,
691 "INSTANCE_MEMORY": memory,
692 "INSTANCE_VCPUS": vcpus,
693 "INSTANCE_DISK_TEMPLATE": disk_template,
694 "INSTANCE_HYPERVISOR": hypervisor_name,
698 nic_count = len(nics)
699 for idx, (ip, mac, mode, link) in enumerate(nics):
702 env["INSTANCE_NIC%d_IP" % idx] = ip
703 env["INSTANCE_NIC%d_MAC" % idx] = mac
704 env["INSTANCE_NIC%d_MODE" % idx] = mode
705 env["INSTANCE_NIC%d_LINK" % idx] = link
706 if mode == constants.NIC_MODE_BRIDGED:
707 env["INSTANCE_NIC%d_BRIDGE" % idx] = link
711 env["INSTANCE_NIC_COUNT"] = nic_count
714 disk_count = len(disks)
715 for idx, (size, mode) in enumerate(disks):
716 env["INSTANCE_DISK%d_SIZE" % idx] = size
717 env["INSTANCE_DISK%d_MODE" % idx] = mode
721 env["INSTANCE_DISK_COUNT"] = disk_count
723 for source, kind in [(bep, "BE"), (hvp, "HV")]:
724 for key, value in source.items():
725 env["INSTANCE_%s_%s" % (kind, key)] = value
730 def _NICListToTuple(lu, nics):
731 """Build a list of nic information tuples.
733 This list is suitable to be passed to _BuildInstanceHookEnv or as a return
734 value in LUQueryInstanceData.
736 @type lu: L{LogicalUnit}
737 @param lu: the logical unit on whose behalf we execute
738 @type nics: list of L{objects.NIC}
739 @param nics: list of nics to convert to hooks tuples
743 c_nicparams = lu.cfg.GetClusterInfo().nicparams[constants.PP_DEFAULT]
747 filled_params = objects.FillDict(c_nicparams, nic.nicparams)
748 mode = filled_params[constants.NIC_MODE]
749 link = filled_params[constants.NIC_LINK]
750 hooks_nics.append((ip, mac, mode, link))
754 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
755 """Builds instance related env variables for hooks from an object.
757 @type lu: L{LogicalUnit}
758 @param lu: the logical unit on whose behalf we execute
759 @type instance: L{objects.Instance}
760 @param instance: the instance for which we should build the
763 @param override: dictionary with key/values that will override
766 @return: the hook environment dictionary
769 cluster = lu.cfg.GetClusterInfo()
770 bep = cluster.FillBE(instance)
771 hvp = cluster.FillHV(instance)
773 'name': instance.name,
774 'primary_node': instance.primary_node,
775 'secondary_nodes': instance.secondary_nodes,
776 'os_type': instance.os,
777 'status': instance.admin_up,
778 'memory': bep[constants.BE_MEMORY],
779 'vcpus': bep[constants.BE_VCPUS],
780 'nics': _NICListToTuple(lu, instance.nics),
781 'disk_template': instance.disk_template,
782 'disks': [(disk.size, disk.mode) for disk in instance.disks],
785 'hypervisor_name': instance.hypervisor,
788 args.update(override)
789 return _BuildInstanceHookEnv(**args) # pylint: disable-msg=W0142
792 def _AdjustCandidatePool(lu, exceptions):
793 """Adjust the candidate pool after node operations.
796 mod_list = lu.cfg.MaintainCandidatePool(exceptions)
798 lu.LogInfo("Promoted nodes to master candidate role: %s",
799 utils.CommaJoin(node.name for node in mod_list))
800 for name in mod_list:
801 lu.context.ReaddNode(name)
802 mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
804 lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
808 def _DecideSelfPromotion(lu, exceptions=None):
809 """Decide whether I should promote myself as a master candidate.
812 cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
813 mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
814 # the new node will increase mc_max with one, so:
815 mc_should = min(mc_should + 1, cp_size)
816 return mc_now < mc_should
819 def _CheckNicsBridgesExist(lu, target_nics, target_node,
820 profile=constants.PP_DEFAULT):
821 """Check that the brigdes needed by a list of nics exist.
824 c_nicparams = lu.cfg.GetClusterInfo().nicparams[profile]
825 paramslist = [objects.FillDict(c_nicparams, nic.nicparams)
826 for nic in target_nics]
827 brlist = [params[constants.NIC_LINK] for params in paramslist
828 if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
830 result = lu.rpc.call_bridges_exist(target_node, brlist)
831 result.Raise("Error checking bridges on destination node '%s'" %
832 target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
835 def _CheckInstanceBridgesExist(lu, instance, node=None):
836 """Check that the brigdes needed by an instance exist.
840 node = instance.primary_node
841 _CheckNicsBridgesExist(lu, instance.nics, node)
844 def _CheckOSVariant(os_obj, name):
845 """Check whether an OS name conforms to the os variants specification.
847 @type os_obj: L{objects.OS}
848 @param os_obj: OS object to check
850 @param name: OS name passed by the user, to check for validity
853 if not os_obj.supported_variants:
856 variant = name.split("+", 1)[1]
858 raise errors.OpPrereqError("OS name must include a variant",
861 if variant not in os_obj.supported_variants:
862 raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
865 def _GetNodeInstancesInner(cfg, fn):
866 return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
869 def _GetNodeInstances(cfg, node_name):
870 """Returns a list of all primary and secondary instances on a node.
874 return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
877 def _GetNodePrimaryInstances(cfg, node_name):
878 """Returns primary instances on a node.
881 return _GetNodeInstancesInner(cfg,
882 lambda inst: node_name == inst.primary_node)
885 def _GetNodeSecondaryInstances(cfg, node_name):
886 """Returns secondary instances on a node.
889 return _GetNodeInstancesInner(cfg,
890 lambda inst: node_name in inst.secondary_nodes)
893 def _GetStorageTypeArgs(cfg, storage_type):
894 """Returns the arguments for a storage type.
897 # Special case for file storage
898 if storage_type == constants.ST_FILE:
899 # storage.FileStorage wants a list of storage directories
900 return [[cfg.GetFileStorageDir()]]
905 def _FindFaultyInstanceDisks(cfg, rpc, instance, node_name, prereq):
908 for dev in instance.disks:
909 cfg.SetDiskID(dev, node_name)
911 result = rpc.call_blockdev_getmirrorstatus(node_name, instance.disks)
912 result.Raise("Failed to get disk status from node %s" % node_name,
913 prereq=prereq, ecode=errors.ECODE_ENVIRON)
915 for idx, bdev_status in enumerate(result.payload):
916 if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
922 def _FormatTimestamp(secs):
923 """Formats a Unix timestamp with the local timezone.
926 return time.strftime("%F %T %Z", time.gmtime(secs))
929 class LUPostInitCluster(LogicalUnit):
930 """Logical unit for running hooks after cluster initialization.
933 HPATH = "cluster-init"
934 HTYPE = constants.HTYPE_CLUSTER
937 def BuildHooksEnv(self):
941 env = {"OP_TARGET": self.cfg.GetClusterName()}
942 mn = self.cfg.GetMasterNode()
945 def CheckPrereq(self):
946 """No prerequisites to check.
951 def Exec(self, feedback_fn):
958 class LUDestroyCluster(LogicalUnit):
959 """Logical unit for destroying the cluster.
962 HPATH = "cluster-destroy"
963 HTYPE = constants.HTYPE_CLUSTER
966 def BuildHooksEnv(self):
970 env = {"OP_TARGET": self.cfg.GetClusterName()}
973 def CheckPrereq(self):
974 """Check prerequisites.
976 This checks whether the cluster is empty.
978 Any errors are signaled by raising errors.OpPrereqError.
981 master = self.cfg.GetMasterNode()
983 nodelist = self.cfg.GetNodeList()
984 if len(nodelist) != 1 or nodelist[0] != master:
985 raise errors.OpPrereqError("There are still %d node(s) in"
986 " this cluster." % (len(nodelist) - 1),
988 instancelist = self.cfg.GetInstanceList()
990 raise errors.OpPrereqError("There are still %d instance(s) in"
991 " this cluster." % len(instancelist),
994 def Exec(self, feedback_fn):
995 """Destroys the cluster.
998 master = self.cfg.GetMasterNode()
999 modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
1001 # Run post hooks on master node before it's removed
1002 hm = self.proc.hmclass(self.rpc.call_hooks_runner, self)
1004 hm.RunPhase(constants.HOOKS_PHASE_POST, [master])
1006 # pylint: disable-msg=W0702
1007 self.LogWarning("Errors occurred running hooks on %s" % master)
1009 result = self.rpc.call_node_stop_master(master, False)
1010 result.Raise("Could not disable the master role")
1012 if modify_ssh_setup:
1013 priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
1014 utils.CreateBackup(priv_key)
1015 utils.CreateBackup(pub_key)
1020 def _VerifyCertificateInner(filename, expired, not_before, not_after, now,
1021 warn_days=constants.SSL_CERT_EXPIRATION_WARN,
1022 error_days=constants.SSL_CERT_EXPIRATION_ERROR):
1023 """Verifies certificate details for LUVerifyCluster.
1027 msg = "Certificate %s is expired" % filename
1029 if not_before is not None and not_after is not None:
1030 msg += (" (valid from %s to %s)" %
1031 (_FormatTimestamp(not_before),
1032 _FormatTimestamp(not_after)))
1033 elif not_before is not None:
1034 msg += " (valid from %s)" % _FormatTimestamp(not_before)
1035 elif not_after is not None:
1036 msg += " (valid until %s)" % _FormatTimestamp(not_after)
1038 return (LUVerifyCluster.ETYPE_ERROR, msg)
1040 elif not_before is not None and not_before > now:
1041 return (LUVerifyCluster.ETYPE_WARNING,
1042 "Certificate %s not yet valid (valid from %s)" %
1043 (filename, _FormatTimestamp(not_before)))
1045 elif not_after is not None:
1046 remaining_days = int((not_after - now) / (24 * 3600))
1048 msg = ("Certificate %s expires in %d days" % (filename, remaining_days))
1050 if remaining_days <= error_days:
1051 return (LUVerifyCluster.ETYPE_ERROR, msg)
1053 if remaining_days <= warn_days:
1054 return (LUVerifyCluster.ETYPE_WARNING, msg)
1059 def _VerifyCertificate(filename):
1060 """Verifies a certificate for LUVerifyCluster.
1062 @type filename: string
1063 @param filename: Path to PEM file
1067 cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1068 utils.ReadFile(filename))
1069 except Exception, err: # pylint: disable-msg=W0703
1070 return (LUVerifyCluster.ETYPE_ERROR,
1071 "Failed to load X509 certificate %s: %s" % (filename, err))
1073 # Depending on the pyOpenSSL version, this can just return (None, None)
1074 (not_before, not_after) = utils.GetX509CertValidity(cert)
1076 return _VerifyCertificateInner(filename, cert.has_expired(),
1077 not_before, not_after, time.time())
1080 class LUVerifyCluster(LogicalUnit):
1081 """Verifies the cluster status.
1084 HPATH = "cluster-verify"
1085 HTYPE = constants.HTYPE_CLUSTER
1086 _OP_REQP = ["skip_checks", "verbose", "error_codes", "debug_simulate_errors"]
1089 TCLUSTER = "cluster"
1091 TINSTANCE = "instance"
1093 ECLUSTERCFG = (TCLUSTER, "ECLUSTERCFG")
1094 ECLUSTERCERT = (TCLUSTER, "ECLUSTERCERT")
1095 EINSTANCEBADNODE = (TINSTANCE, "EINSTANCEBADNODE")
1096 EINSTANCEDOWN = (TINSTANCE, "EINSTANCEDOWN")
1097 EINSTANCELAYOUT = (TINSTANCE, "EINSTANCELAYOUT")
1098 EINSTANCEMISSINGDISK = (TINSTANCE, "EINSTANCEMISSINGDISK")
1099 EINSTANCEMISSINGDISK = (TINSTANCE, "EINSTANCEMISSINGDISK")
1100 EINSTANCEWRONGNODE = (TINSTANCE, "EINSTANCEWRONGNODE")
1101 ENODEDRBD = (TNODE, "ENODEDRBD")
1102 ENODEFILECHECK = (TNODE, "ENODEFILECHECK")
1103 ENODEHOOKS = (TNODE, "ENODEHOOKS")
1104 ENODEHV = (TNODE, "ENODEHV")
1105 ENODELVM = (TNODE, "ENODELVM")
1106 ENODEN1 = (TNODE, "ENODEN1")
1107 ENODENET = (TNODE, "ENODENET")
1108 ENODEORPHANINSTANCE = (TNODE, "ENODEORPHANINSTANCE")
1109 ENODEORPHANLV = (TNODE, "ENODEORPHANLV")
1110 ENODERPC = (TNODE, "ENODERPC")
1111 ENODESSH = (TNODE, "ENODESSH")
1112 ENODEVERSION = (TNODE, "ENODEVERSION")
1113 ENODESETUP = (TNODE, "ENODESETUP")
1114 ENODETIME = (TNODE, "ENODETIME")
1116 ETYPE_FIELD = "code"
1117 ETYPE_ERROR = "ERROR"
1118 ETYPE_WARNING = "WARNING"
1120 class NodeImage(object):
1121 """A class representing the logical and physical status of a node.
1123 @ivar volumes: a structure as returned from
1124 L{ganeti.backend.GetVolumeList} (runtime)
1125 @ivar instances: a list of running instances (runtime)
1126 @ivar pinst: list of configured primary instances (config)
1127 @ivar sinst: list of configured secondary instances (config)
1128 @ivar sbp: diction of {secondary-node: list of instances} of all peers
1129 of this node (config)
1130 @ivar mfree: free memory, as reported by hypervisor (runtime)
1131 @ivar dfree: free disk, as reported by the node (runtime)
1132 @ivar offline: the offline status (config)
1133 @type rpc_fail: boolean
1134 @ivar rpc_fail: whether the RPC verify call was successfull (overall,
1135 not whether the individual keys were correct) (runtime)
1136 @type lvm_fail: boolean
1137 @ivar lvm_fail: whether the RPC call didn't return valid LVM data
1138 @type hyp_fail: boolean
1139 @ivar hyp_fail: whether the RPC call didn't return the instance list
1140 @type ghost: boolean
1141 @ivar ghost: whether this is a known node or not (config)
1144 def __init__(self, offline=False):
1152 self.offline = offline
1153 self.rpc_fail = False
1154 self.lvm_fail = False
1155 self.hyp_fail = False
1158 def ExpandNames(self):
1159 self.needed_locks = {
1160 locking.LEVEL_NODE: locking.ALL_SET,
1161 locking.LEVEL_INSTANCE: locking.ALL_SET,
1163 self.share_locks = dict.fromkeys(locking.LEVELS, 1)
1165 def _Error(self, ecode, item, msg, *args, **kwargs):
1166 """Format an error message.
1168 Based on the opcode's error_codes parameter, either format a
1169 parseable error code, or a simpler error string.
1171 This must be called only from Exec and functions called from Exec.
1174 ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1176 # first complete the msg
1179 # then format the whole message
1180 if self.op.error_codes:
1181 msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1187 msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1188 # and finally report it via the feedback_fn
1189 self._feedback_fn(" - %s" % msg)
1191 def _ErrorIf(self, cond, *args, **kwargs):
1192 """Log an error message if the passed condition is True.
1195 cond = bool(cond) or self.op.debug_simulate_errors
1197 self._Error(*args, **kwargs)
1198 # do not mark the operation as failed for WARN cases only
1199 if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
1200 self.bad = self.bad or cond
1202 def _VerifyNode(self, ninfo, nresult):
1203 """Run multiple tests against a node.
1207 - compares ganeti version
1208 - checks vg existence and size > 20G
1209 - checks config file checksum
1210 - checks ssh to other nodes
1212 @type ninfo: L{objects.Node}
1213 @param ninfo: the node to check
1214 @param nresult: the results from the node
1216 @return: whether overall this call was successful (and we can expect
1217 reasonable values in the respose)
1221 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1223 # main result, nresult should be a non-empty dict
1224 test = not nresult or not isinstance(nresult, dict)
1225 _ErrorIf(test, self.ENODERPC, node,
1226 "unable to verify node: no data returned")
1230 # compares ganeti version
1231 local_version = constants.PROTOCOL_VERSION
1232 remote_version = nresult.get("version", None)
1233 test = not (remote_version and
1234 isinstance(remote_version, (list, tuple)) and
1235 len(remote_version) == 2)
1236 _ErrorIf(test, self.ENODERPC, node,
1237 "connection to node returned invalid data")
1241 test = local_version != remote_version[0]
1242 _ErrorIf(test, self.ENODEVERSION, node,
1243 "incompatible protocol versions: master %s,"
1244 " node %s", local_version, remote_version[0])
1248 # node seems compatible, we can actually try to look into its results
1250 # full package version
1251 self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
1252 self.ENODEVERSION, node,
1253 "software version mismatch: master %s, node %s",
1254 constants.RELEASE_VERSION, remote_version[1],
1255 code=self.ETYPE_WARNING)
1257 hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
1258 if isinstance(hyp_result, dict):
1259 for hv_name, hv_result in hyp_result.iteritems():
1260 test = hv_result is not None
1261 _ErrorIf(test, self.ENODEHV, node,
1262 "hypervisor %s verify failure: '%s'", hv_name, hv_result)
1265 test = nresult.get(constants.NV_NODESETUP,
1266 ["Missing NODESETUP results"])
1267 _ErrorIf(test, self.ENODESETUP, node, "node setup error: %s",
1272 def _VerifyNodeTime(self, ninfo, nresult,
1273 nvinfo_starttime, nvinfo_endtime):
1274 """Check the node time.
1276 @type ninfo: L{objects.Node}
1277 @param ninfo: the node to check
1278 @param nresult: the remote results for the node
1279 @param nvinfo_starttime: the start time of the RPC call
1280 @param nvinfo_endtime: the end time of the RPC call
1284 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1286 ntime = nresult.get(constants.NV_TIME, None)
1288 ntime_merged = utils.MergeTime(ntime)
1289 except (ValueError, TypeError):
1290 _ErrorIf(True, self.ENODETIME, node, "Node returned invalid time")
1293 if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
1294 ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
1295 elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
1296 ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
1300 _ErrorIf(ntime_diff is not None, self.ENODETIME, node,
1301 "Node time diverges by at least %s from master node time",
1304 def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
1305 """Check the node time.
1307 @type ninfo: L{objects.Node}
1308 @param ninfo: the node to check
1309 @param nresult: the remote results for the node
1310 @param vg_name: the configured VG name
1317 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1319 # checks vg existence and size > 20G
1320 vglist = nresult.get(constants.NV_VGLIST, None)
1322 _ErrorIf(test, self.ENODELVM, node, "unable to check volume groups")
1324 vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
1325 constants.MIN_VG_SIZE)
1326 _ErrorIf(vgstatus, self.ENODELVM, node, vgstatus)
1329 pvlist = nresult.get(constants.NV_PVLIST, None)
1330 test = pvlist is None
1331 _ErrorIf(test, self.ENODELVM, node, "Can't get PV list from node")
1333 # check that ':' is not present in PV names, since it's a
1334 # special character for lvcreate (denotes the range of PEs to
1336 for _, pvname, owner_vg in pvlist:
1337 test = ":" in pvname
1338 _ErrorIf(test, self.ENODELVM, node, "Invalid character ':' in PV"
1339 " '%s' of VG '%s'", pvname, owner_vg)
1341 def _VerifyNodeNetwork(self, ninfo, nresult):
1342 """Check the node time.
1344 @type ninfo: L{objects.Node}
1345 @param ninfo: the node to check
1346 @param nresult: the remote results for the node
1350 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1352 test = constants.NV_NODELIST not in nresult
1353 _ErrorIf(test, self.ENODESSH, node,
1354 "node hasn't returned node ssh connectivity data")
1356 if nresult[constants.NV_NODELIST]:
1357 for a_node, a_msg in nresult[constants.NV_NODELIST].items():
1358 _ErrorIf(True, self.ENODESSH, node,
1359 "ssh communication with node '%s': %s", a_node, a_msg)
1361 test = constants.NV_NODENETTEST not in nresult
1362 _ErrorIf(test, self.ENODENET, node,
1363 "node hasn't returned node tcp connectivity data")
1365 if nresult[constants.NV_NODENETTEST]:
1366 nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
1368 _ErrorIf(True, self.ENODENET, node,
1369 "tcp communication with node '%s': %s",
1370 anode, nresult[constants.NV_NODENETTEST][anode])
1372 def _VerifyInstance(self, instance, instanceconfig, node_image):
1373 """Verify an instance.
1375 This function checks to see if the required block devices are
1376 available on the instance's node.
1379 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1380 node_current = instanceconfig.primary_node
1382 node_vol_should = {}
1383 instanceconfig.MapLVsByNode(node_vol_should)
1385 for node in node_vol_should:
1386 n_img = node_image[node]
1387 if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
1388 # ignore missing volumes on offline or broken nodes
1390 for volume in node_vol_should[node]:
1391 test = volume not in n_img.volumes
1392 _ErrorIf(test, self.EINSTANCEMISSINGDISK, instance,
1393 "volume %s missing on node %s", volume, node)
1395 if instanceconfig.admin_up:
1396 pri_img = node_image[node_current]
1397 test = instance not in pri_img.instances and not pri_img.offline
1398 _ErrorIf(test, self.EINSTANCEDOWN, instance,
1399 "instance not running on its primary node %s",
1402 for node, n_img in node_image.items():
1403 if (not node == node_current):
1404 test = instance in n_img.instances
1405 _ErrorIf(test, self.EINSTANCEWRONGNODE, instance,
1406 "instance should not run on node %s", node)
1408 def _VerifyOrphanVolumes(self, node_vol_should, node_image):
1409 """Verify if there are any unknown volumes in the cluster.
1411 The .os, .swap and backup volumes are ignored. All other volumes are
1412 reported as unknown.
1415 for node, n_img in node_image.items():
1416 if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
1417 # skip non-healthy nodes
1419 for volume in n_img.volumes:
1420 test = (node not in node_vol_should or
1421 volume not in node_vol_should[node])
1422 self._ErrorIf(test, self.ENODEORPHANLV, node,
1423 "volume %s is unknown", volume)
1425 def _VerifyOrphanInstances(self, instancelist, node_image):
1426 """Verify the list of running instances.
1428 This checks what instances are running but unknown to the cluster.
1431 for node, n_img in node_image.items():
1432 for o_inst in n_img.instances:
1433 test = o_inst not in instancelist
1434 self._ErrorIf(test, self.ENODEORPHANINSTANCE, node,
1435 "instance %s on node %s should not exist", o_inst, node)
1437 def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
1438 """Verify N+1 Memory Resilience.
1440 Check that if one single node dies we can still start all the
1441 instances it was primary for.
1444 for node, n_img in node_image.items():
1445 # This code checks that every node which is now listed as
1446 # secondary has enough memory to host all instances it is
1447 # supposed to should a single other node in the cluster fail.
1448 # FIXME: not ready for failover to an arbitrary node
1449 # FIXME: does not support file-backed instances
1450 # WARNING: we currently take into account down instances as well
1451 # as up ones, considering that even if they're down someone
1452 # might want to start them even in the event of a node failure.
1453 for prinode, instances in n_img.sbp.items():
1455 for instance in instances:
1456 bep = self.cfg.GetClusterInfo().FillBE(instance_cfg[instance])
1457 if bep[constants.BE_AUTO_BALANCE]:
1458 needed_mem += bep[constants.BE_MEMORY]
1459 test = n_img.mfree < needed_mem
1460 self._ErrorIf(test, self.ENODEN1, node,
1461 "not enough memory on to accommodate"
1462 " failovers should peer node %s fail", prinode)
1464 def _VerifyNodeFiles(self, ninfo, nresult, file_list, local_cksum,
1466 """Verifies and computes the node required file checksums.
1468 @type ninfo: L{objects.Node}
1469 @param ninfo: the node to check
1470 @param nresult: the remote results for the node
1471 @param file_list: required list of files
1472 @param local_cksum: dictionary of local files and their checksums
1473 @param master_files: list of files that only masters should have
1477 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1479 remote_cksum = nresult.get(constants.NV_FILELIST, None)
1480 test = not isinstance(remote_cksum, dict)
1481 _ErrorIf(test, self.ENODEFILECHECK, node,
1482 "node hasn't returned file checksum data")
1486 for file_name in file_list:
1487 node_is_mc = ninfo.master_candidate
1488 must_have = (file_name not in master_files) or node_is_mc
1490 test1 = file_name not in remote_cksum
1492 test2 = not test1 and remote_cksum[file_name] != local_cksum[file_name]
1494 test3 = not test1 and remote_cksum[file_name] == local_cksum[file_name]
1495 _ErrorIf(test1 and must_have, self.ENODEFILECHECK, node,
1496 "file '%s' missing", file_name)
1497 _ErrorIf(test2 and must_have, self.ENODEFILECHECK, node,
1498 "file '%s' has wrong checksum", file_name)
1499 # not candidate and this is not a must-have file
1500 _ErrorIf(test2 and not must_have, self.ENODEFILECHECK, node,
1501 "file '%s' should not exist on non master"
1502 " candidates (and the file is outdated)", file_name)
1503 # all good, except non-master/non-must have combination
1504 _ErrorIf(test3 and not must_have, self.ENODEFILECHECK, node,
1505 "file '%s' should not exist"
1506 " on non master candidates", file_name)
1508 def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_map):
1509 """Verifies and the node DRBD status.
1511 @type ninfo: L{objects.Node}
1512 @param ninfo: the node to check
1513 @param nresult: the remote results for the node
1514 @param instanceinfo: the dict of instances
1515 @param drbd_map: the DRBD map as returned by
1516 L{ganeti.config.ConfigWriter.ComputeDRBDMap}
1520 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1522 # compute the DRBD minors
1524 for minor, instance in drbd_map[node].items():
1525 test = instance not in instanceinfo
1526 _ErrorIf(test, self.ECLUSTERCFG, None,
1527 "ghost instance '%s' in temporary DRBD map", instance)
1528 # ghost instance should not be running, but otherwise we
1529 # don't give double warnings (both ghost instance and
1530 # unallocated minor in use)
1532 node_drbd[minor] = (instance, False)
1534 instance = instanceinfo[instance]
1535 node_drbd[minor] = (instance.name, instance.admin_up)
1537 # and now check them
1538 used_minors = nresult.get(constants.NV_DRBDLIST, [])
1539 test = not isinstance(used_minors, (tuple, list))
1540 _ErrorIf(test, self.ENODEDRBD, node,
1541 "cannot parse drbd status file: %s", str(used_minors))
1543 # we cannot check drbd status
1546 for minor, (iname, must_exist) in node_drbd.items():
1547 test = minor not in used_minors and must_exist
1548 _ErrorIf(test, self.ENODEDRBD, node,
1549 "drbd minor %d of instance %s is not active", minor, iname)
1550 for minor in used_minors:
1551 test = minor not in node_drbd
1552 _ErrorIf(test, self.ENODEDRBD, node,
1553 "unallocated drbd minor %d is in use", minor)
1555 def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
1556 """Verifies and updates the node volume data.
1558 This function will update a L{NodeImage}'s internal structures
1559 with data from the remote call.
1561 @type ninfo: L{objects.Node}
1562 @param ninfo: the node to check
1563 @param nresult: the remote results for the node
1564 @param nimg: the node image object
1565 @param vg_name: the configured VG name
1569 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1571 nimg.lvm_fail = True
1572 lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
1575 elif isinstance(lvdata, basestring):
1576 _ErrorIf(True, self.ENODELVM, node, "LVM problem on node: %s",
1577 utils.SafeEncode(lvdata))
1578 elif not isinstance(lvdata, dict):
1579 _ErrorIf(True, self.ENODELVM, node, "rpc call to node failed (lvlist)")
1581 nimg.volumes = lvdata
1582 nimg.lvm_fail = False
1584 def _UpdateNodeInstances(self, ninfo, nresult, nimg):
1585 """Verifies and updates the node instance list.
1587 If the listing was successful, then updates this node's instance
1588 list. Otherwise, it marks the RPC call as failed for the instance
1591 @type ninfo: L{objects.Node}
1592 @param ninfo: the node to check
1593 @param nresult: the remote results for the node
1594 @param nimg: the node image object
1597 idata = nresult.get(constants.NV_INSTANCELIST, None)
1598 test = not isinstance(idata, list)
1599 self._ErrorIf(test, self.ENODEHV, ninfo.name, "rpc call to node failed"
1600 " (instancelist): %s", utils.SafeEncode(str(idata)))
1602 nimg.hyp_fail = True
1604 nimg.instances = idata
1606 def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
1607 """Verifies and computes a node information map
1609 @type ninfo: L{objects.Node}
1610 @param ninfo: the node to check
1611 @param nresult: the remote results for the node
1612 @param nimg: the node image object
1613 @param vg_name: the configured VG name
1617 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1619 # try to read free memory (from the hypervisor)
1620 hv_info = nresult.get(constants.NV_HVINFO, None)
1621 test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
1622 _ErrorIf(test, self.ENODEHV, node, "rpc call to node failed (hvinfo)")
1625 nimg.mfree = int(hv_info["memory_free"])
1626 except (ValueError, TypeError):
1627 _ErrorIf(True, self.ENODERPC, node,
1628 "node returned invalid nodeinfo, check hypervisor")
1630 # FIXME: devise a free space model for file based instances as well
1631 if vg_name is not None:
1632 test = (constants.NV_VGLIST not in nresult or
1633 vg_name not in nresult[constants.NV_VGLIST])
1634 _ErrorIf(test, self.ENODELVM, node,
1635 "node didn't return data for the volume group '%s'"
1636 " - it is either missing or broken", vg_name)
1639 nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
1640 except (ValueError, TypeError):
1641 _ErrorIf(True, self.ENODERPC, node,
1642 "node returned invalid LVM info, check LVM status")
1644 def CheckPrereq(self):
1645 """Check prerequisites.
1647 Transform the list of checks we're going to skip into a set and check that
1648 all its members are valid.
1651 self.skip_set = frozenset(self.op.skip_checks)
1652 if not constants.VERIFY_OPTIONAL_CHECKS.issuperset(self.skip_set):
1653 raise errors.OpPrereqError("Invalid checks to be skipped specified",
1656 def BuildHooksEnv(self):
1659 Cluster-Verify hooks just ran in the post phase and their failure makes
1660 the output be logged in the verify output and the verification to fail.
1663 all_nodes = self.cfg.GetNodeList()
1665 "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
1667 for node in self.cfg.GetAllNodesInfo().values():
1668 env["NODE_TAGS_%s" % node.name] = " ".join(node.GetTags())
1670 return env, [], all_nodes
1672 def Exec(self, feedback_fn):
1673 """Verify integrity of cluster, performing various test on nodes.
1677 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1678 verbose = self.op.verbose
1679 self._feedback_fn = feedback_fn
1680 feedback_fn("* Verifying global settings")
1681 for msg in self.cfg.VerifyConfig():
1682 _ErrorIf(True, self.ECLUSTERCFG, None, msg)
1684 # Check the cluster certificates
1685 for cert_filename in constants.ALL_CERT_FILES:
1686 (errcode, msg) = _VerifyCertificate(cert_filename)
1687 _ErrorIf(errcode, self.ECLUSTERCERT, None, msg, code=errcode)
1689 vg_name = self.cfg.GetVGName()
1690 hypervisors = self.cfg.GetClusterInfo().enabled_hypervisors
1691 nodelist = utils.NiceSort(self.cfg.GetNodeList())
1692 nodeinfo = [self.cfg.GetNodeInfo(nname) for nname in nodelist]
1693 instancelist = utils.NiceSort(self.cfg.GetInstanceList())
1694 instanceinfo = dict((iname, self.cfg.GetInstanceInfo(iname))
1695 for iname in instancelist)
1696 i_non_redundant = [] # Non redundant instances
1697 i_non_a_balanced = [] # Non auto-balanced instances
1698 n_offline = 0 # Count of offline nodes
1699 n_drained = 0 # Count of nodes being drained
1700 node_vol_should = {}
1702 # FIXME: verify OS list
1703 # do local checksums
1704 master_files = [constants.CLUSTER_CONF_FILE]
1706 file_names = ssconf.SimpleStore().GetFileList()
1707 file_names.extend(constants.ALL_CERT_FILES)
1708 file_names.extend(master_files)
1710 local_checksums = utils.FingerprintFiles(file_names)
1712 feedback_fn("* Gathering data (%d nodes)" % len(nodelist))
1713 node_verify_param = {
1714 constants.NV_FILELIST: file_names,
1715 constants.NV_NODELIST: [node.name for node in nodeinfo
1716 if not node.offline],
1717 constants.NV_HYPERVISOR: hypervisors,
1718 constants.NV_NODENETTEST: [(node.name, node.primary_ip,
1719 node.secondary_ip) for node in nodeinfo
1720 if not node.offline],
1721 constants.NV_INSTANCELIST: hypervisors,
1722 constants.NV_VERSION: None,
1723 constants.NV_HVINFO: self.cfg.GetHypervisorType(),
1724 constants.NV_NODESETUP: None,
1725 constants.NV_TIME: None,
1728 if vg_name is not None:
1729 node_verify_param[constants.NV_VGLIST] = None
1730 node_verify_param[constants.NV_LVLIST] = vg_name
1731 node_verify_param[constants.NV_PVLIST] = [vg_name]
1732 node_verify_param[constants.NV_DRBDLIST] = None
1734 # Build our expected cluster state
1735 node_image = dict((node.name, self.NodeImage(offline=node.offline))
1736 for node in nodeinfo)
1738 for instance in instancelist:
1739 inst_config = instanceinfo[instance]
1741 for nname in inst_config.all_nodes:
1742 if nname not in node_image:
1744 gnode = self.NodeImage()
1746 node_image[nname] = gnode
1748 inst_config.MapLVsByNode(node_vol_should)
1750 pnode = inst_config.primary_node
1751 node_image[pnode].pinst.append(instance)
1753 for snode in inst_config.secondary_nodes:
1754 nimg = node_image[snode]
1755 nimg.sinst.append(instance)
1756 if pnode not in nimg.sbp:
1757 nimg.sbp[pnode] = []
1758 nimg.sbp[pnode].append(instance)
1760 # At this point, we have the in-memory data structures complete,
1761 # except for the runtime information, which we'll gather next
1763 # Due to the way our RPC system works, exact response times cannot be
1764 # guaranteed (e.g. a broken node could run into a timeout). By keeping the
1765 # time before and after executing the request, we can at least have a time
1767 nvinfo_starttime = time.time()
1768 all_nvinfo = self.rpc.call_node_verify(nodelist, node_verify_param,
1769 self.cfg.GetClusterName())
1770 nvinfo_endtime = time.time()
1772 cluster = self.cfg.GetClusterInfo()
1773 master_node = self.cfg.GetMasterNode()
1774 all_drbd_map = self.cfg.ComputeDRBDMap()
1776 feedback_fn("* Verifying node status")
1777 for node_i in nodeinfo:
1779 nimg = node_image[node]
1783 feedback_fn("* Skipping offline node %s" % (node,))
1787 if node == master_node:
1789 elif node_i.master_candidate:
1790 ntype = "master candidate"
1791 elif node_i.drained:
1797 feedback_fn("* Verifying node %s (%s)" % (node, ntype))
1799 msg = all_nvinfo[node].fail_msg
1800 _ErrorIf(msg, self.ENODERPC, node, "while contacting node: %s", msg)
1802 nimg.rpc_fail = True
1805 nresult = all_nvinfo[node].payload
1807 nimg.call_ok = self._VerifyNode(node_i, nresult)
1808 self._VerifyNodeNetwork(node_i, nresult)
1809 self._VerifyNodeLVM(node_i, nresult, vg_name)
1810 self._VerifyNodeFiles(node_i, nresult, file_names, local_checksums,
1812 self._VerifyNodeDrbd(node_i, nresult, instanceinfo, all_drbd_map)
1813 self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
1815 self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
1816 self._UpdateNodeInstances(node_i, nresult, nimg)
1817 self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
1819 feedback_fn("* Verifying instance status")
1820 for instance in instancelist:
1822 feedback_fn("* Verifying instance %s" % instance)
1823 inst_config = instanceinfo[instance]
1824 self._VerifyInstance(instance, inst_config, node_image)
1825 inst_nodes_offline = []
1827 pnode = inst_config.primary_node
1828 pnode_img = node_image[pnode]
1829 _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
1830 self.ENODERPC, pnode, "instance %s, connection to"
1831 " primary node failed", instance)
1833 if pnode_img.offline:
1834 inst_nodes_offline.append(pnode)
1836 # If the instance is non-redundant we cannot survive losing its primary
1837 # node, so we are not N+1 compliant. On the other hand we have no disk
1838 # templates with more than one secondary so that situation is not well
1840 # FIXME: does not support file-backed instances
1841 if not inst_config.secondary_nodes:
1842 i_non_redundant.append(instance)
1843 _ErrorIf(len(inst_config.secondary_nodes) > 1, self.EINSTANCELAYOUT,
1844 instance, "instance has multiple secondary nodes: %s",
1845 utils.CommaJoin(inst_config.secondary_nodes),
1846 code=self.ETYPE_WARNING)
1848 if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
1849 i_non_a_balanced.append(instance)
1851 for snode in inst_config.secondary_nodes:
1852 s_img = node_image[snode]
1853 _ErrorIf(s_img.rpc_fail and not s_img.offline, self.ENODERPC, snode,
1854 "instance %s, connection to secondary node failed", instance)
1857 inst_nodes_offline.append(snode)
1859 # warn that the instance lives on offline nodes
1860 _ErrorIf(inst_nodes_offline, self.EINSTANCEBADNODE, instance,
1861 "instance lives on offline node(s) %s",
1862 utils.CommaJoin(inst_nodes_offline))
1863 # ... or ghost nodes
1864 for node in inst_config.all_nodes:
1865 _ErrorIf(node_image[node].ghost, self.EINSTANCEBADNODE, instance,
1866 "instance lives on ghost node %s", node)
1868 feedback_fn("* Verifying orphan volumes")
1869 self._VerifyOrphanVolumes(node_vol_should, node_image)
1871 feedback_fn("* Verifying oprhan instances")
1872 self._VerifyOrphanInstances(instancelist, node_image)
1874 if constants.VERIFY_NPLUSONE_MEM not in self.skip_set:
1875 feedback_fn("* Verifying N+1 Memory redundancy")
1876 self._VerifyNPlusOneMemory(node_image, instanceinfo)
1878 feedback_fn("* Other Notes")
1880 feedback_fn(" - NOTICE: %d non-redundant instance(s) found."
1881 % len(i_non_redundant))
1883 if i_non_a_balanced:
1884 feedback_fn(" - NOTICE: %d non-auto-balanced instance(s) found."
1885 % len(i_non_a_balanced))
1888 feedback_fn(" - NOTICE: %d offline node(s) found." % n_offline)
1891 feedback_fn(" - NOTICE: %d drained node(s) found." % n_drained)
1895 def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
1896 """Analyze the post-hooks' result
1898 This method analyses the hook result, handles it, and sends some
1899 nicely-formatted feedback back to the user.
1901 @param phase: one of L{constants.HOOKS_PHASE_POST} or
1902 L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
1903 @param hooks_results: the results of the multi-node hooks rpc call
1904 @param feedback_fn: function used send feedback back to the caller
1905 @param lu_result: previous Exec result
1906 @return: the new Exec result, based on the previous result
1910 # We only really run POST phase hooks, and are only interested in
1912 if phase == constants.HOOKS_PHASE_POST:
1913 # Used to change hooks' output to proper indentation
1914 indent_re = re.compile('^', re.M)
1915 feedback_fn("* Hooks Results")
1916 assert hooks_results, "invalid result from hooks"
1918 for node_name in hooks_results:
1919 res = hooks_results[node_name]
1921 test = msg and not res.offline
1922 self._ErrorIf(test, self.ENODEHOOKS, node_name,
1923 "Communication failure in hooks execution: %s", msg)
1924 if res.offline or msg:
1925 # No need to investigate payload if node is offline or gave an error.
1926 # override manually lu_result here as _ErrorIf only
1927 # overrides self.bad
1930 for script, hkr, output in res.payload:
1931 test = hkr == constants.HKR_FAIL
1932 self._ErrorIf(test, self.ENODEHOOKS, node_name,
1933 "Script %s failed, output:", script)
1935 output = indent_re.sub(' ', output)
1936 feedback_fn("%s" % output)
1942 class LUVerifyDisks(NoHooksLU):
1943 """Verifies the cluster disks status.
1949 def ExpandNames(self):
1950 self.needed_locks = {
1951 locking.LEVEL_NODE: locking.ALL_SET,
1952 locking.LEVEL_INSTANCE: locking.ALL_SET,
1954 self.share_locks = dict.fromkeys(locking.LEVELS, 1)
1956 def CheckPrereq(self):
1957 """Check prerequisites.
1959 This has no prerequisites.
1964 def Exec(self, feedback_fn):
1965 """Verify integrity of cluster disks.
1967 @rtype: tuple of three items
1968 @return: a tuple of (dict of node-to-node_error, list of instances
1969 which need activate-disks, dict of instance: (node, volume) for
1973 result = res_nodes, res_instances, res_missing = {}, [], {}
1975 vg_name = self.cfg.GetVGName()
1976 nodes = utils.NiceSort(self.cfg.GetNodeList())
1977 instances = [self.cfg.GetInstanceInfo(name)
1978 for name in self.cfg.GetInstanceList()]
1981 for inst in instances:
1983 if (not inst.admin_up or
1984 inst.disk_template not in constants.DTS_NET_MIRROR):
1986 inst.MapLVsByNode(inst_lvs)
1987 # transform { iname: {node: [vol,],},} to {(node, vol): iname}
1988 for node, vol_list in inst_lvs.iteritems():
1989 for vol in vol_list:
1990 nv_dict[(node, vol)] = inst
1995 node_lvs = self.rpc.call_lv_list(nodes, vg_name)
1999 node_res = node_lvs[node]
2000 if node_res.offline:
2002 msg = node_res.fail_msg
2004 logging.warning("Error enumerating LVs on node %s: %s", node, msg)
2005 res_nodes[node] = msg
2008 lvs = node_res.payload
2009 for lv_name, (_, _, lv_online) in lvs.items():
2010 inst = nv_dict.pop((node, lv_name), None)
2011 if (not lv_online and inst is not None
2012 and inst.name not in res_instances):
2013 res_instances.append(inst.name)
2015 # any leftover items in nv_dict are missing LVs, let's arrange the
2017 for key, inst in nv_dict.iteritems():
2018 if inst.name not in res_missing:
2019 res_missing[inst.name] = []
2020 res_missing[inst.name].append(key)
2025 class LURepairDiskSizes(NoHooksLU):
2026 """Verifies the cluster disks sizes.
2029 _OP_REQP = ["instances"]
2032 def ExpandNames(self):
2033 if not isinstance(self.op.instances, list):
2034 raise errors.OpPrereqError("Invalid argument type 'instances'",
2037 if self.op.instances:
2038 self.wanted_names = []
2039 for name in self.op.instances:
2040 full_name = _ExpandInstanceName(self.cfg, name)
2041 self.wanted_names.append(full_name)
2042 self.needed_locks = {
2043 locking.LEVEL_NODE: [],
2044 locking.LEVEL_INSTANCE: self.wanted_names,
2046 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2048 self.wanted_names = None
2049 self.needed_locks = {
2050 locking.LEVEL_NODE: locking.ALL_SET,
2051 locking.LEVEL_INSTANCE: locking.ALL_SET,
2053 self.share_locks = dict(((i, 1) for i in locking.LEVELS))
2055 def DeclareLocks(self, level):
2056 if level == locking.LEVEL_NODE and self.wanted_names is not None:
2057 self._LockInstancesNodes(primary_only=True)
2059 def CheckPrereq(self):
2060 """Check prerequisites.
2062 This only checks the optional instance list against the existing names.
2065 if self.wanted_names is None:
2066 self.wanted_names = self.acquired_locks[locking.LEVEL_INSTANCE]
2068 self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name
2069 in self.wanted_names]
2071 def _EnsureChildSizes(self, disk):
2072 """Ensure children of the disk have the needed disk size.
2074 This is valid mainly for DRBD8 and fixes an issue where the
2075 children have smaller disk size.
2077 @param disk: an L{ganeti.objects.Disk} object
2080 if disk.dev_type == constants.LD_DRBD8:
2081 assert disk.children, "Empty children for DRBD8?"
2082 fchild = disk.children[0]
2083 mismatch = fchild.size < disk.size
2085 self.LogInfo("Child disk has size %d, parent %d, fixing",
2086 fchild.size, disk.size)
2087 fchild.size = disk.size
2089 # and we recurse on this child only, not on the metadev
2090 return self._EnsureChildSizes(fchild) or mismatch
2094 def Exec(self, feedback_fn):
2095 """Verify the size of cluster disks.
2098 # TODO: check child disks too
2099 # TODO: check differences in size between primary/secondary nodes
2101 for instance in self.wanted_instances:
2102 pnode = instance.primary_node
2103 if pnode not in per_node_disks:
2104 per_node_disks[pnode] = []
2105 for idx, disk in enumerate(instance.disks):
2106 per_node_disks[pnode].append((instance, idx, disk))
2109 for node, dskl in per_node_disks.items():
2110 newl = [v[2].Copy() for v in dskl]
2112 self.cfg.SetDiskID(dsk, node)
2113 result = self.rpc.call_blockdev_getsizes(node, newl)
2115 self.LogWarning("Failure in blockdev_getsizes call to node"
2116 " %s, ignoring", node)
2118 if len(result.data) != len(dskl):
2119 self.LogWarning("Invalid result from node %s, ignoring node results",
2122 for ((instance, idx, disk), size) in zip(dskl, result.data):
2124 self.LogWarning("Disk %d of instance %s did not return size"
2125 " information, ignoring", idx, instance.name)
2127 if not isinstance(size, (int, long)):
2128 self.LogWarning("Disk %d of instance %s did not return valid"
2129 " size information, ignoring", idx, instance.name)
2132 if size != disk.size:
2133 self.LogInfo("Disk %d of instance %s has mismatched size,"
2134 " correcting: recorded %d, actual %d", idx,
2135 instance.name, disk.size, size)
2137 self.cfg.Update(instance, feedback_fn)
2138 changed.append((instance.name, idx, size))
2139 if self._EnsureChildSizes(disk):
2140 self.cfg.Update(instance, feedback_fn)
2141 changed.append((instance.name, idx, disk.size))
2145 class LURenameCluster(LogicalUnit):
2146 """Rename the cluster.
2149 HPATH = "cluster-rename"
2150 HTYPE = constants.HTYPE_CLUSTER
2153 def BuildHooksEnv(self):
2158 "OP_TARGET": self.cfg.GetClusterName(),
2159 "NEW_NAME": self.op.name,
2161 mn = self.cfg.GetMasterNode()
2162 all_nodes = self.cfg.GetNodeList()
2163 return env, [mn], all_nodes
2165 def CheckPrereq(self):
2166 """Verify that the passed name is a valid one.
2169 hostname = utils.GetHostInfo(self.op.name)
2171 new_name = hostname.name
2172 self.ip = new_ip = hostname.ip
2173 old_name = self.cfg.GetClusterName()
2174 old_ip = self.cfg.GetMasterIP()
2175 if new_name == old_name and new_ip == old_ip:
2176 raise errors.OpPrereqError("Neither the name nor the IP address of the"
2177 " cluster has changed",
2179 if new_ip != old_ip:
2180 if utils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
2181 raise errors.OpPrereqError("The given cluster IP address (%s) is"
2182 " reachable on the network. Aborting." %
2183 new_ip, errors.ECODE_NOTUNIQUE)
2185 self.op.name = new_name
2187 def Exec(self, feedback_fn):
2188 """Rename the cluster.
2191 clustername = self.op.name
2194 # shutdown the master IP
2195 master = self.cfg.GetMasterNode()
2196 result = self.rpc.call_node_stop_master(master, False)
2197 result.Raise("Could not disable the master role")
2200 cluster = self.cfg.GetClusterInfo()
2201 cluster.cluster_name = clustername
2202 cluster.master_ip = ip
2203 self.cfg.Update(cluster, feedback_fn)
2205 # update the known hosts file
2206 ssh.WriteKnownHostsFile(self.cfg, constants.SSH_KNOWN_HOSTS_FILE)
2207 node_list = self.cfg.GetNodeList()
2209 node_list.remove(master)
2212 result = self.rpc.call_upload_file(node_list,
2213 constants.SSH_KNOWN_HOSTS_FILE)
2214 for to_node, to_result in result.iteritems():
2215 msg = to_result.fail_msg
2217 msg = ("Copy of file %s to node %s failed: %s" %
2218 (constants.SSH_KNOWN_HOSTS_FILE, to_node, msg))
2219 self.proc.LogWarning(msg)
2222 result = self.rpc.call_node_start_master(master, False, False)
2223 msg = result.fail_msg
2225 self.LogWarning("Could not re-enable the master role on"
2226 " the master, please restart manually: %s", msg)
2229 def _RecursiveCheckIfLVMBased(disk):
2230 """Check if the given disk or its children are lvm-based.
2232 @type disk: L{objects.Disk}
2233 @param disk: the disk to check
2235 @return: boolean indicating whether a LD_LV dev_type was found or not
2239 for chdisk in disk.children:
2240 if _RecursiveCheckIfLVMBased(chdisk):
2242 return disk.dev_type == constants.LD_LV
2245 class LUSetClusterParams(LogicalUnit):
2246 """Change the parameters of the cluster.
2249 HPATH = "cluster-modify"
2250 HTYPE = constants.HTYPE_CLUSTER
2254 def CheckArguments(self):
2258 if not hasattr(self.op, "candidate_pool_size"):
2259 self.op.candidate_pool_size = None
2260 if self.op.candidate_pool_size is not None:
2262 self.op.candidate_pool_size = int(self.op.candidate_pool_size)
2263 except (ValueError, TypeError), err:
2264 raise errors.OpPrereqError("Invalid candidate_pool_size value: %s" %
2265 str(err), errors.ECODE_INVAL)
2266 if self.op.candidate_pool_size < 1:
2267 raise errors.OpPrereqError("At least one master candidate needed",
2270 _CheckBooleanOpField(self.op, "maintain_node_health")
2272 if self.op.uid_pool:
2273 uidpool.CheckUidPool(self.op.uid_pool)
2275 if self.op.add_uids:
2276 uidpool.CheckUidPool(self.op.add_uids)
2278 if self.op.remove_uids:
2279 uidpool.CheckUidPool(self.op.remove_uids)
2281 def ExpandNames(self):
2282 # FIXME: in the future maybe other cluster params won't require checking on
2283 # all nodes to be modified.
2284 self.needed_locks = {
2285 locking.LEVEL_NODE: locking.ALL_SET,
2287 self.share_locks[locking.LEVEL_NODE] = 1
2289 def BuildHooksEnv(self):
2294 "OP_TARGET": self.cfg.GetClusterName(),
2295 "NEW_VG_NAME": self.op.vg_name,
2297 mn = self.cfg.GetMasterNode()
2298 return env, [mn], [mn]
2300 def CheckPrereq(self):
2301 """Check prerequisites.
2303 This checks whether the given params don't conflict and
2304 if the given volume group is valid.
2307 if self.op.vg_name is not None and not self.op.vg_name:
2308 instances = self.cfg.GetAllInstancesInfo().values()
2309 for inst in instances:
2310 for disk in inst.disks:
2311 if _RecursiveCheckIfLVMBased(disk):
2312 raise errors.OpPrereqError("Cannot disable lvm storage while"
2313 " lvm-based instances exist",
2316 node_list = self.acquired_locks[locking.LEVEL_NODE]
2318 # if vg_name not None, checks given volume group on all nodes
2320 vglist = self.rpc.call_vg_list(node_list)
2321 for node in node_list:
2322 msg = vglist[node].fail_msg
2324 # ignoring down node
2325 self.LogWarning("Error while gathering data on node %s"
2326 " (ignoring node): %s", node, msg)
2328 vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
2330 constants.MIN_VG_SIZE)
2332 raise errors.OpPrereqError("Error on node '%s': %s" %
2333 (node, vgstatus), errors.ECODE_ENVIRON)
2335 self.cluster = cluster = self.cfg.GetClusterInfo()
2336 # validate params changes
2337 if self.op.beparams:
2338 utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
2339 self.new_beparams = objects.FillDict(
2340 cluster.beparams[constants.PP_DEFAULT], self.op.beparams)
2342 if self.op.nicparams:
2343 utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
2344 self.new_nicparams = objects.FillDict(
2345 cluster.nicparams[constants.PP_DEFAULT], self.op.nicparams)
2346 objects.NIC.CheckParameterSyntax(self.new_nicparams)
2349 # check all instances for consistency
2350 for instance in self.cfg.GetAllInstancesInfo().values():
2351 for nic_idx, nic in enumerate(instance.nics):
2352 params_copy = copy.deepcopy(nic.nicparams)
2353 params_filled = objects.FillDict(self.new_nicparams, params_copy)
2355 # check parameter syntax
2357 objects.NIC.CheckParameterSyntax(params_filled)
2358 except errors.ConfigurationError, err:
2359 nic_errors.append("Instance %s, nic/%d: %s" %
2360 (instance.name, nic_idx, err))
2362 # if we're moving instances to routed, check that they have an ip
2363 target_mode = params_filled[constants.NIC_MODE]
2364 if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
2365 nic_errors.append("Instance %s, nic/%d: routed nick with no ip" %
2366 (instance.name, nic_idx))
2368 raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
2369 "\n".join(nic_errors))
2371 # hypervisor list/parameters
2372 self.new_hvparams = objects.FillDict(cluster.hvparams, {})
2373 if self.op.hvparams:
2374 if not isinstance(self.op.hvparams, dict):
2375 raise errors.OpPrereqError("Invalid 'hvparams' parameter on input",
2377 for hv_name, hv_dict in self.op.hvparams.items():
2378 if hv_name not in self.new_hvparams:
2379 self.new_hvparams[hv_name] = hv_dict
2381 self.new_hvparams[hv_name].update(hv_dict)
2383 # os hypervisor parameters
2384 self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
2386 if not isinstance(self.op.os_hvp, dict):
2387 raise errors.OpPrereqError("Invalid 'os_hvp' parameter on input",
2389 for os_name, hvs in self.op.os_hvp.items():
2390 if not isinstance(hvs, dict):
2391 raise errors.OpPrereqError(("Invalid 'os_hvp' parameter on"
2392 " input"), errors.ECODE_INVAL)
2393 if os_name not in self.new_os_hvp:
2394 self.new_os_hvp[os_name] = hvs
2396 for hv_name, hv_dict in hvs.items():
2397 if hv_name not in self.new_os_hvp[os_name]:
2398 self.new_os_hvp[os_name][hv_name] = hv_dict
2400 self.new_os_hvp[os_name][hv_name].update(hv_dict)
2402 if self.op.enabled_hypervisors is not None:
2403 self.hv_list = self.op.enabled_hypervisors
2404 if not self.hv_list:
2405 raise errors.OpPrereqError("Enabled hypervisors list must contain at"
2406 " least one member",
2408 invalid_hvs = set(self.hv_list) - constants.HYPER_TYPES
2410 raise errors.OpPrereqError("Enabled hypervisors contains invalid"
2412 utils.CommaJoin(invalid_hvs),
2415 self.hv_list = cluster.enabled_hypervisors
2417 if self.op.hvparams or self.op.enabled_hypervisors is not None:
2418 # either the enabled list has changed, or the parameters have, validate
2419 for hv_name, hv_params in self.new_hvparams.items():
2420 if ((self.op.hvparams and hv_name in self.op.hvparams) or
2421 (self.op.enabled_hypervisors and
2422 hv_name in self.op.enabled_hypervisors)):
2423 # either this is a new hypervisor, or its parameters have changed
2424 hv_class = hypervisor.GetHypervisor(hv_name)
2425 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2426 hv_class.CheckParameterSyntax(hv_params)
2427 _CheckHVParams(self, node_list, hv_name, hv_params)
2430 # no need to check any newly-enabled hypervisors, since the
2431 # defaults have already been checked in the above code-block
2432 for os_name, os_hvp in self.new_os_hvp.items():
2433 for hv_name, hv_params in os_hvp.items():
2434 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2435 # we need to fill in the new os_hvp on top of the actual hv_p
2436 cluster_defaults = self.new_hvparams.get(hv_name, {})
2437 new_osp = objects.FillDict(cluster_defaults, hv_params)
2438 hv_class = hypervisor.GetHypervisor(hv_name)
2439 hv_class.CheckParameterSyntax(new_osp)
2440 _CheckHVParams(self, node_list, hv_name, new_osp)
2443 def Exec(self, feedback_fn):
2444 """Change the parameters of the cluster.
2447 if self.op.vg_name is not None:
2448 new_volume = self.op.vg_name
2451 if new_volume != self.cfg.GetVGName():
2452 self.cfg.SetVGName(new_volume)
2454 feedback_fn("Cluster LVM configuration already in desired"
2455 " state, not changing")
2456 if self.op.hvparams:
2457 self.cluster.hvparams = self.new_hvparams
2459 self.cluster.os_hvp = self.new_os_hvp
2460 if self.op.enabled_hypervisors is not None:
2461 self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
2462 if self.op.beparams:
2463 self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
2464 if self.op.nicparams:
2465 self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
2467 if self.op.candidate_pool_size is not None:
2468 self.cluster.candidate_pool_size = self.op.candidate_pool_size
2469 # we need to update the pool size here, otherwise the save will fail
2470 _AdjustCandidatePool(self, [])
2472 if self.op.maintain_node_health is not None:
2473 self.cluster.maintain_node_health = self.op.maintain_node_health
2475 if self.op.add_uids is not None:
2476 uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
2478 if self.op.remove_uids is not None:
2479 uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
2481 if self.op.uid_pool is not None:
2482 self.cluster.uid_pool = self.op.uid_pool
2484 self.cfg.Update(self.cluster, feedback_fn)
2487 def _RedistributeAncillaryFiles(lu, additional_nodes=None):
2488 """Distribute additional files which are part of the cluster configuration.
2490 ConfigWriter takes care of distributing the config and ssconf files, but
2491 there are more files which should be distributed to all nodes. This function
2492 makes sure those are copied.
2494 @param lu: calling logical unit
2495 @param additional_nodes: list of nodes not in the config to distribute to
2498 # 1. Gather target nodes
2499 myself = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
2500 dist_nodes = lu.cfg.GetOnlineNodeList()
2501 if additional_nodes is not None:
2502 dist_nodes.extend(additional_nodes)
2503 if myself.name in dist_nodes:
2504 dist_nodes.remove(myself.name)
2506 # 2. Gather files to distribute
2507 dist_files = set([constants.ETC_HOSTS,
2508 constants.SSH_KNOWN_HOSTS_FILE,
2509 constants.RAPI_CERT_FILE,
2510 constants.RAPI_USERS_FILE,
2511 constants.CONFD_HMAC_KEY,
2514 enabled_hypervisors = lu.cfg.GetClusterInfo().enabled_hypervisors
2515 for hv_name in enabled_hypervisors:
2516 hv_class = hypervisor.GetHypervisor(hv_name)
2517 dist_files.update(hv_class.GetAncillaryFiles())
2519 # 3. Perform the files upload
2520 for fname in dist_files:
2521 if os.path.exists(fname):
2522 result = lu.rpc.call_upload_file(dist_nodes, fname)
2523 for to_node, to_result in result.items():
2524 msg = to_result.fail_msg
2526 msg = ("Copy of file %s to node %s failed: %s" %
2527 (fname, to_node, msg))
2528 lu.proc.LogWarning(msg)
2531 class LURedistributeConfig(NoHooksLU):
2532 """Force the redistribution of cluster configuration.
2534 This is a very simple LU.
2540 def ExpandNames(self):
2541 self.needed_locks = {
2542 locking.LEVEL_NODE: locking.ALL_SET,
2544 self.share_locks[locking.LEVEL_NODE] = 1
2546 def CheckPrereq(self):
2547 """Check prerequisites.
2551 def Exec(self, feedback_fn):
2552 """Redistribute the configuration.
2555 self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
2556 _RedistributeAncillaryFiles(self)
2559 def _WaitForSync(lu, instance, oneshot=False):
2560 """Sleep and poll for an instance's disk to sync.
2563 if not instance.disks:
2567 lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
2569 node = instance.primary_node
2571 for dev in instance.disks:
2572 lu.cfg.SetDiskID(dev, node)
2574 # TODO: Convert to utils.Retry
2577 degr_retries = 10 # in seconds, as we sleep 1 second each time
2581 cumul_degraded = False
2582 rstats = lu.rpc.call_blockdev_getmirrorstatus(node, instance.disks)
2583 msg = rstats.fail_msg
2585 lu.LogWarning("Can't get any data from node %s: %s", node, msg)
2588 raise errors.RemoteError("Can't contact node %s for mirror data,"
2589 " aborting." % node)
2592 rstats = rstats.payload
2594 for i, mstat in enumerate(rstats):
2596 lu.LogWarning("Can't compute data for node %s/%s",
2597 node, instance.disks[i].iv_name)
2600 cumul_degraded = (cumul_degraded or
2601 (mstat.is_degraded and mstat.sync_percent is None))
2602 if mstat.sync_percent is not None:
2604 if mstat.estimated_time is not None:
2605 rem_time = "%d estimated seconds remaining" % mstat.estimated_time
2606 max_time = mstat.estimated_time
2608 rem_time = "no time estimate"
2609 lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
2610 (instance.disks[i].iv_name, mstat.sync_percent,
2613 # if we're done but degraded, let's do a few small retries, to
2614 # make sure we see a stable and not transient situation; therefore
2615 # we force restart of the loop
2616 if (done or oneshot) and cumul_degraded and degr_retries > 0:
2617 logging.info("Degraded disks found, %d retries left", degr_retries)
2625 time.sleep(min(60, max_time))
2628 lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
2629 return not cumul_degraded
2632 def _CheckDiskConsistency(lu, dev, node, on_primary, ldisk=False):
2633 """Check that mirrors are not degraded.
2635 The ldisk parameter, if True, will change the test from the
2636 is_degraded attribute (which represents overall non-ok status for
2637 the device(s)) to the ldisk (representing the local storage status).
2640 lu.cfg.SetDiskID(dev, node)
2644 if on_primary or dev.AssembleOnSecondary():
2645 rstats = lu.rpc.call_blockdev_find(node, dev)
2646 msg = rstats.fail_msg
2648 lu.LogWarning("Can't find disk on node %s: %s", node, msg)
2650 elif not rstats.payload:
2651 lu.LogWarning("Can't find disk on node %s", node)
2655 result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
2657 result = result and not rstats.payload.is_degraded
2660 for child in dev.children:
2661 result = result and _CheckDiskConsistency(lu, child, node, on_primary)
2666 class LUDiagnoseOS(NoHooksLU):
2667 """Logical unit for OS diagnose/query.
2670 _OP_REQP = ["output_fields", "names"]
2672 _FIELDS_STATIC = utils.FieldSet()
2673 _FIELDS_DYNAMIC = utils.FieldSet("name", "valid", "node_status", "variants")
2674 # Fields that need calculation of global os validity
2675 _FIELDS_NEEDVALID = frozenset(["valid", "variants"])
2677 def ExpandNames(self):
2679 raise errors.OpPrereqError("Selective OS query not supported",
2682 _CheckOutputFields(static=self._FIELDS_STATIC,
2683 dynamic=self._FIELDS_DYNAMIC,
2684 selected=self.op.output_fields)
2686 # Lock all nodes, in shared mode
2687 # Temporary removal of locks, should be reverted later
2688 # TODO: reintroduce locks when they are lighter-weight
2689 self.needed_locks = {}
2690 #self.share_locks[locking.LEVEL_NODE] = 1
2691 #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
2693 def CheckPrereq(self):
2694 """Check prerequisites.
2699 def _DiagnoseByOS(rlist):
2700 """Remaps a per-node return list into an a per-os per-node dictionary
2702 @param rlist: a map with node names as keys and OS objects as values
2705 @return: a dictionary with osnames as keys and as value another map, with
2706 nodes as keys and tuples of (path, status, diagnose) as values, eg::
2708 {"debian-etch": {"node1": [(/usr/lib/..., True, ""),
2709 (/srv/..., False, "invalid api")],
2710 "node2": [(/srv/..., True, "")]}
2715 # we build here the list of nodes that didn't fail the RPC (at RPC
2716 # level), so that nodes with a non-responding node daemon don't
2717 # make all OSes invalid
2718 good_nodes = [node_name for node_name in rlist
2719 if not rlist[node_name].fail_msg]
2720 for node_name, nr in rlist.items():
2721 if nr.fail_msg or not nr.payload:
2723 for name, path, status, diagnose, variants in nr.payload:
2724 if name not in all_os:
2725 # build a list of nodes for this os containing empty lists
2726 # for each node in node_list
2728 for nname in good_nodes:
2729 all_os[name][nname] = []
2730 all_os[name][node_name].append((path, status, diagnose, variants))
2733 def Exec(self, feedback_fn):
2734 """Compute the list of OSes.
2737 valid_nodes = [node for node in self.cfg.GetOnlineNodeList()]
2738 node_data = self.rpc.call_os_diagnose(valid_nodes)
2739 pol = self._DiagnoseByOS(node_data)
2741 calc_valid = self._FIELDS_NEEDVALID.intersection(self.op.output_fields)
2742 calc_variants = "variants" in self.op.output_fields
2744 for os_name, os_data in pol.items():
2749 for osl in os_data.values():
2750 valid = valid and osl and osl[0][1]
2755 node_variants = osl[0][3]
2756 if variants is None:
2757 variants = node_variants
2759 variants = [v for v in variants if v in node_variants]
2761 for field in self.op.output_fields:
2764 elif field == "valid":
2766 elif field == "node_status":
2767 # this is just a copy of the dict
2769 for node_name, nos_list in os_data.items():
2770 val[node_name] = nos_list
2771 elif field == "variants":
2774 raise errors.ParameterError(field)
2781 class LURemoveNode(LogicalUnit):
2782 """Logical unit for removing a node.
2785 HPATH = "node-remove"
2786 HTYPE = constants.HTYPE_NODE
2787 _OP_REQP = ["node_name"]
2789 def BuildHooksEnv(self):
2792 This doesn't run on the target node in the pre phase as a failed
2793 node would then be impossible to remove.
2797 "OP_TARGET": self.op.node_name,
2798 "NODE_NAME": self.op.node_name,
2800 all_nodes = self.cfg.GetNodeList()
2802 all_nodes.remove(self.op.node_name)
2804 logging.warning("Node %s which is about to be removed not found"
2805 " in the all nodes list", self.op.node_name)
2806 return env, all_nodes, all_nodes
2808 def CheckPrereq(self):
2809 """Check prerequisites.
2812 - the node exists in the configuration
2813 - it does not have primary or secondary instances
2814 - it's not the master
2816 Any errors are signaled by raising errors.OpPrereqError.
2819 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
2820 node = self.cfg.GetNodeInfo(self.op.node_name)
2821 assert node is not None
2823 instance_list = self.cfg.GetInstanceList()
2825 masternode = self.cfg.GetMasterNode()
2826 if node.name == masternode:
2827 raise errors.OpPrereqError("Node is the master node,"
2828 " you need to failover first.",
2831 for instance_name in instance_list:
2832 instance = self.cfg.GetInstanceInfo(instance_name)
2833 if node.name in instance.all_nodes:
2834 raise errors.OpPrereqError("Instance %s is still running on the node,"
2835 " please remove first." % instance_name,
2837 self.op.node_name = node.name
2840 def Exec(self, feedback_fn):
2841 """Removes the node from the cluster.
2845 logging.info("Stopping the node daemon and removing configs from node %s",
2848 modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
2850 # Promote nodes to master candidate as needed
2851 _AdjustCandidatePool(self, exceptions=[node.name])
2852 self.context.RemoveNode(node.name)
2854 # Run post hooks on the node before it's removed
2855 hm = self.proc.hmclass(self.rpc.call_hooks_runner, self)
2857 hm.RunPhase(constants.HOOKS_PHASE_POST, [node.name])
2859 # pylint: disable-msg=W0702
2860 self.LogWarning("Errors occurred running hooks on %s" % node.name)
2862 result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
2863 msg = result.fail_msg
2865 self.LogWarning("Errors encountered on the remote node while leaving"
2866 " the cluster: %s", msg)
2869 class LUQueryNodes(NoHooksLU):
2870 """Logical unit for querying nodes.
2873 # pylint: disable-msg=W0142
2874 _OP_REQP = ["output_fields", "names", "use_locking"]
2877 _SIMPLE_FIELDS = ["name", "serial_no", "ctime", "mtime", "uuid",
2878 "master_candidate", "offline", "drained"]
2880 _FIELDS_DYNAMIC = utils.FieldSet(
2882 "mtotal", "mnode", "mfree",
2884 "ctotal", "cnodes", "csockets",
2887 _FIELDS_STATIC = utils.FieldSet(*[
2888 "pinst_cnt", "sinst_cnt",
2889 "pinst_list", "sinst_list",
2890 "pip", "sip", "tags",
2892 "role"] + _SIMPLE_FIELDS
2895 def ExpandNames(self):
2896 _CheckOutputFields(static=self._FIELDS_STATIC,
2897 dynamic=self._FIELDS_DYNAMIC,
2898 selected=self.op.output_fields)
2900 self.needed_locks = {}
2901 self.share_locks[locking.LEVEL_NODE] = 1
2904 self.wanted = _GetWantedNodes(self, self.op.names)
2906 self.wanted = locking.ALL_SET
2908 self.do_node_query = self._FIELDS_STATIC.NonMatching(self.op.output_fields)
2909 self.do_locking = self.do_node_query and self.op.use_locking
2911 # if we don't request only static fields, we need to lock the nodes
2912 self.needed_locks[locking.LEVEL_NODE] = self.wanted
2914 def CheckPrereq(self):
2915 """Check prerequisites.
2918 # The validation of the node list is done in the _GetWantedNodes,
2919 # if non empty, and if empty, there's no validation to do
2922 def Exec(self, feedback_fn):
2923 """Computes the list of nodes and their attributes.
2926 all_info = self.cfg.GetAllNodesInfo()
2928 nodenames = self.acquired_locks[locking.LEVEL_NODE]
2929 elif self.wanted != locking.ALL_SET:
2930 nodenames = self.wanted
2931 missing = set(nodenames).difference(all_info.keys())
2933 raise errors.OpExecError(
2934 "Some nodes were removed before retrieving their data: %s" % missing)
2936 nodenames = all_info.keys()
2938 nodenames = utils.NiceSort(nodenames)
2939 nodelist = [all_info[name] for name in nodenames]
2941 # begin data gathering
2943 if self.do_node_query:
2945 node_data = self.rpc.call_node_info(nodenames, self.cfg.GetVGName(),
2946 self.cfg.GetHypervisorType())
2947 for name in nodenames:
2948 nodeinfo = node_data[name]
2949 if not nodeinfo.fail_msg and nodeinfo.payload:
2950 nodeinfo = nodeinfo.payload
2951 fn = utils.TryConvert
2953 "mtotal": fn(int, nodeinfo.get('memory_total', None)),
2954 "mnode": fn(int, nodeinfo.get('memory_dom0', None)),
2955 "mfree": fn(int, nodeinfo.get('memory_free', None)),
2956 "dtotal": fn(int, nodeinfo.get('vg_size', None)),
2957 "dfree": fn(int, nodeinfo.get('vg_free', None)),
2958 "ctotal": fn(int, nodeinfo.get('cpu_total', None)),
2959 "bootid": nodeinfo.get('bootid', None),
2960 "cnodes": fn(int, nodeinfo.get('cpu_nodes', None)),
2961 "csockets": fn(int, nodeinfo.get('cpu_sockets', None)),
2964 live_data[name] = {}
2966 live_data = dict.fromkeys(nodenames, {})
2968 node_to_primary = dict([(name, set()) for name in nodenames])
2969 node_to_secondary = dict([(name, set()) for name in nodenames])
2971 inst_fields = frozenset(("pinst_cnt", "pinst_list",
2972 "sinst_cnt", "sinst_list"))
2973 if inst_fields & frozenset(self.op.output_fields):
2974 inst_data = self.cfg.GetAllInstancesInfo()
2976 for inst in inst_data.values():
2977 if inst.primary_node in node_to_primary:
2978 node_to_primary[inst.primary_node].add(inst.name)
2979 for secnode in inst.secondary_nodes:
2980 if secnode in node_to_secondary:
2981 node_to_secondary[secnode].add(inst.name)
2983 master_node = self.cfg.GetMasterNode()
2985 # end data gathering
2988 for node in nodelist:
2990 for field in self.op.output_fields:
2991 if field in self._SIMPLE_FIELDS:
2992 val = getattr(node, field)
2993 elif field == "pinst_list":
2994 val = list(node_to_primary[node.name])
2995 elif field == "sinst_list":
2996 val = list(node_to_secondary[node.name])
2997 elif field == "pinst_cnt":
2998 val = len(node_to_primary[node.name])
2999 elif field == "sinst_cnt":
3000 val = len(node_to_secondary[node.name])
3001 elif field == "pip":
3002 val = node.primary_ip
3003 elif field == "sip":
3004 val = node.secondary_ip
3005 elif field == "tags":
3006 val = list(node.GetTags())
3007 elif field == "master":
3008 val = node.name == master_node
3009 elif self._FIELDS_DYNAMIC.Matches(field):
3010 val = live_data[node.name].get(field, None)
3011 elif field == "role":
3012 if node.name == master_node:
3014 elif node.master_candidate:
3023 raise errors.ParameterError(field)
3024 node_output.append(val)
3025 output.append(node_output)
3030 class LUQueryNodeVolumes(NoHooksLU):
3031 """Logical unit for getting volumes on node(s).
3034 _OP_REQP = ["nodes", "output_fields"]
3036 _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
3037 _FIELDS_STATIC = utils.FieldSet("node")
3039 def ExpandNames(self):
3040 _CheckOutputFields(static=self._FIELDS_STATIC,
3041 dynamic=self._FIELDS_DYNAMIC,
3042 selected=self.op.output_fields)
3044 self.needed_locks = {}
3045 self.share_locks[locking.LEVEL_NODE] = 1
3046 if not self.op.nodes:
3047 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3049 self.needed_locks[locking.LEVEL_NODE] = \
3050 _GetWantedNodes(self, self.op.nodes)
3052 def CheckPrereq(self):
3053 """Check prerequisites.
3055 This checks that the fields required are valid output fields.
3058 self.nodes = self.acquired_locks[locking.LEVEL_NODE]
3060 def Exec(self, feedback_fn):
3061 """Computes the list of nodes and their attributes.
3064 nodenames = self.nodes
3065 volumes = self.rpc.call_node_volumes(nodenames)
3067 ilist = [self.cfg.GetInstanceInfo(iname) for iname
3068 in self.cfg.GetInstanceList()]
3070 lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist])
3073 for node in nodenames:
3074 nresult = volumes[node]
3077 msg = nresult.fail_msg
3079 self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
3082 node_vols = nresult.payload[:]
3083 node_vols.sort(key=lambda vol: vol['dev'])
3085 for vol in node_vols:
3087 for field in self.op.output_fields:
3090 elif field == "phys":
3094 elif field == "name":
3096 elif field == "size":
3097 val = int(float(vol['size']))
3098 elif field == "instance":
3100 if node not in lv_by_node[inst]:
3102 if vol['name'] in lv_by_node[inst][node]:
3108 raise errors.ParameterError(field)
3109 node_output.append(str(val))
3111 output.append(node_output)
3116 class LUQueryNodeStorage(NoHooksLU):
3117 """Logical unit for getting information on storage units on node(s).
3120 _OP_REQP = ["nodes", "storage_type", "output_fields"]
3122 _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
3124 def CheckArguments(self):
3125 _CheckStorageType(self.op.storage_type)
3127 _CheckOutputFields(static=self._FIELDS_STATIC,
3128 dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
3129 selected=self.op.output_fields)
3131 def ExpandNames(self):
3132 self.needed_locks = {}
3133 self.share_locks[locking.LEVEL_NODE] = 1
3136 self.needed_locks[locking.LEVEL_NODE] = \
3137 _GetWantedNodes(self, self.op.nodes)
3139 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3141 def CheckPrereq(self):
3142 """Check prerequisites.
3144 This checks that the fields required are valid output fields.
3147 self.op.name = getattr(self.op, "name", None)
3149 self.nodes = self.acquired_locks[locking.LEVEL_NODE]
3151 def Exec(self, feedback_fn):
3152 """Computes the list of nodes and their attributes.
3155 # Always get name to sort by
3156 if constants.SF_NAME in self.op.output_fields:
3157 fields = self.op.output_fields[:]
3159 fields = [constants.SF_NAME] + self.op.output_fields
3161 # Never ask for node or type as it's only known to the LU
3162 for extra in [constants.SF_NODE, constants.SF_TYPE]:
3163 while extra in fields:
3164 fields.remove(extra)
3166 field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
3167 name_idx = field_idx[constants.SF_NAME]
3169 st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
3170 data = self.rpc.call_storage_list(self.nodes,
3171 self.op.storage_type, st_args,
3172 self.op.name, fields)
3176 for node in utils.NiceSort(self.nodes):
3177 nresult = data[node]
3181 msg = nresult.fail_msg
3183 self.LogWarning("Can't get storage data from node %s: %s", node, msg)
3186 rows = dict([(row[name_idx], row) for row in nresult.payload])
3188 for name in utils.NiceSort(rows.keys()):
3193 for field in self.op.output_fields:
3194 if field == constants.SF_NODE:
3196 elif field == constants.SF_TYPE:
3197 val = self.op.storage_type
3198 elif field in field_idx:
3199 val = row[field_idx[field]]
3201 raise errors.ParameterError(field)
3210 class LUModifyNodeStorage(NoHooksLU):
3211 """Logical unit for modifying a storage volume on a node.
3214 _OP_REQP = ["node_name", "storage_type", "name", "changes"]
3217 def CheckArguments(self):
3218 self.opnode_name = _ExpandNodeName(self.cfg, self.op.node_name)
3220 _CheckStorageType(self.op.storage_type)
3222 def ExpandNames(self):
3223 self.needed_locks = {
3224 locking.LEVEL_NODE: self.op.node_name,
3227 def CheckPrereq(self):
3228 """Check prerequisites.
3231 storage_type = self.op.storage_type
3234 modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
3236 raise errors.OpPrereqError("Storage units of type '%s' can not be"
3237 " modified" % storage_type,
3240 diff = set(self.op.changes.keys()) - modifiable
3242 raise errors.OpPrereqError("The following fields can not be modified for"
3243 " storage units of type '%s': %r" %
3244 (storage_type, list(diff)),
3247 def Exec(self, feedback_fn):
3248 """Computes the list of nodes and their attributes.
3251 st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
3252 result = self.rpc.call_storage_modify(self.op.node_name,
3253 self.op.storage_type, st_args,
3254 self.op.name, self.op.changes)
3255 result.Raise("Failed to modify storage unit '%s' on %s" %
3256 (self.op.name, self.op.node_name))
3259 class LUAddNode(LogicalUnit):
3260 """Logical unit for adding node to the cluster.
3264 HTYPE = constants.HTYPE_NODE
3265 _OP_REQP = ["node_name"]
3267 def CheckArguments(self):
3268 # validate/normalize the node name
3269 self.op.node_name = utils.HostInfo.NormalizeName(self.op.node_name)
3271 def BuildHooksEnv(self):
3274 This will run on all nodes before, and on all nodes + the new node after.
3278 "OP_TARGET": self.op.node_name,
3279 "NODE_NAME": self.op.node_name,
3280 "NODE_PIP": self.op.primary_ip,
3281 "NODE_SIP": self.op.secondary_ip,
3283 nodes_0 = self.cfg.GetNodeList()
3284 nodes_1 = nodes_0 + [self.op.node_name, ]
3285 return env, nodes_0, nodes_1
3287 def CheckPrereq(self):
3288 """Check prerequisites.
3291 - the new node is not already in the config
3293 - its parameters (single/dual homed) matches the cluster
3295 Any errors are signaled by raising errors.OpPrereqError.
3298 node_name = self.op.node_name
3301 dns_data = utils.GetHostInfo(node_name)
3303 node = dns_data.name
3304 primary_ip = self.op.primary_ip = dns_data.ip
3305 secondary_ip = getattr(self.op, "secondary_ip", None)
3306 if secondary_ip is None:
3307 secondary_ip = primary_ip
3308 if not utils.IsValidIP(secondary_ip):
3309 raise errors.OpPrereqError("Invalid secondary IP given",
3311 self.op.secondary_ip = secondary_ip
3313 node_list = cfg.GetNodeList()
3314 if not self.op.readd and node in node_list:
3315 raise errors.OpPrereqError("Node %s is already in the configuration" %
3316 node, errors.ECODE_EXISTS)
3317 elif self.op.readd and node not in node_list:
3318 raise errors.OpPrereqError("Node %s is not in the configuration" % node,
3321 for existing_node_name in node_list:
3322 existing_node = cfg.GetNodeInfo(existing_node_name)
3324 if self.op.readd and node == existing_node_name:
3325 if (existing_node.primary_ip != primary_ip or
3326 existing_node.secondary_ip != secondary_ip):
3327 raise errors.OpPrereqError("Readded node doesn't have the same IP"
3328 " address configuration as before",
3332 if (existing_node.primary_ip == primary_ip or
3333 existing_node.secondary_ip == primary_ip or
3334 existing_node.primary_ip == secondary_ip or
3335 existing_node.secondary_ip == secondary_ip):
3336 raise errors.OpPrereqError("New node ip address(es) conflict with"
3337 " existing node %s" % existing_node.name,
3338 errors.ECODE_NOTUNIQUE)
3340 # check that the type of the node (single versus dual homed) is the
3341 # same as for the master
3342 myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
3343 master_singlehomed = myself.secondary_ip == myself.primary_ip
3344 newbie_singlehomed = secondary_ip == primary_ip
3345 if master_singlehomed != newbie_singlehomed:
3346 if master_singlehomed:
3347 raise errors.OpPrereqError("The master has no private ip but the"
3348 " new node has one",
3351 raise errors.OpPrereqError("The master has a private ip but the"
3352 " new node doesn't have one",
3355 # checks reachability
3356 if not utils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
3357 raise errors.OpPrereqError("Node not reachable by ping",
3358 errors.ECODE_ENVIRON)
3360 if not newbie_singlehomed:
3361 # check reachability from my secondary ip to newbie's secondary ip
3362 if not utils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
3363 source=myself.secondary_ip):
3364 raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
3365 " based ping to noded port",
3366 errors.ECODE_ENVIRON)
3373 self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
3376 self.new_node = self.cfg.GetNodeInfo(node)
3377 assert self.new_node is not None, "Can't retrieve locked node %s" % node
3379 self.new_node = objects.Node(name=node,
3380 primary_ip=primary_ip,
3381 secondary_ip=secondary_ip,
3382 master_candidate=self.master_candidate,
3383 offline=False, drained=False)
3385 def Exec(self, feedback_fn):
3386 """Adds the new node to the cluster.
3389 new_node = self.new_node
3390 node = new_node.name
3392 # for re-adds, reset the offline/drained/master-candidate flags;
3393 # we need to reset here, otherwise offline would prevent RPC calls
3394 # later in the procedure; this also means that if the re-add
3395 # fails, we are left with a non-offlined, broken node
3397 new_node.drained = new_node.offline = False # pylint: disable-msg=W0201
3398 self.LogInfo("Readding a node, the offline/drained flags were reset")
3399 # if we demote the node, we do cleanup later in the procedure
3400 new_node.master_candidate = self.master_candidate
3402 # notify the user about any possible mc promotion
3403 if new_node.master_candidate:
3404 self.LogInfo("Node will be a master candidate")
3406 # check connectivity
3407 result = self.rpc.call_version([node])[node]
3408 result.Raise("Can't get version information from node %s" % node)
3409 if constants.PROTOCOL_VERSION == result.payload:
3410 logging.info("Communication to node %s fine, sw version %s match",
3411 node, result.payload)
3413 raise errors.OpExecError("Version mismatch master version %s,"
3414 " node version %s" %
3415 (constants.PROTOCOL_VERSION, result.payload))
3418 if self.cfg.GetClusterInfo().modify_ssh_setup:
3419 logging.info("Copy ssh key to node %s", node)
3420 priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
3422 keyfiles = [constants.SSH_HOST_DSA_PRIV, constants.SSH_HOST_DSA_PUB,
3423 constants.SSH_HOST_RSA_PRIV, constants.SSH_HOST_RSA_PUB,
3427 keyarray.append(utils.ReadFile(i))
3429 result = self.rpc.call_node_add(node, keyarray[0], keyarray[1],
3430 keyarray[2], keyarray[3], keyarray[4],
3432 result.Raise("Cannot transfer ssh keys to the new node")
3434 # Add node to our /etc/hosts, and add key to known_hosts
3435 if self.cfg.GetClusterInfo().modify_etc_hosts:
3436 utils.AddHostToEtcHosts(new_node.name)
3438 if new_node.secondary_ip != new_node.primary_ip:
3439 result = self.rpc.call_node_has_ip_address(new_node.name,
3440 new_node.secondary_ip)
3441 result.Raise("Failure checking secondary ip on node %s" % new_node.name,
3442 prereq=True, ecode=errors.ECODE_ENVIRON)
3443 if not result.payload:
3444 raise errors.OpExecError("Node claims it doesn't have the secondary ip"
3445 " you gave (%s). Please fix and re-run this"
3446 " command." % new_node.secondary_ip)
3448 node_verify_list = [self.cfg.GetMasterNode()]
3449 node_verify_param = {
3450 constants.NV_NODELIST: [node],
3451 # TODO: do a node-net-test as well?
3454 result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
3455 self.cfg.GetClusterName())
3456 for verifier in node_verify_list:
3457 result[verifier].Raise("Cannot communicate with node %s" % verifier)
3458 nl_payload = result[verifier].payload[constants.NV_NODELIST]
3460 for failed in nl_payload:
3461 feedback_fn("ssh/hostname verification failed"
3462 " (checking from %s): %s" %
3463 (verifier, nl_payload[failed]))
3464 raise errors.OpExecError("ssh/hostname verification failed.")
3467 _RedistributeAncillaryFiles(self)
3468 self.context.ReaddNode(new_node)
3469 # make sure we redistribute the config
3470 self.cfg.Update(new_node, feedback_fn)
3471 # and make sure the new node will not have old files around
3472 if not new_node.master_candidate:
3473 result = self.rpc.call_node_demote_from_mc(new_node.name)
3474 msg = result.fail_msg
3476 self.LogWarning("Node failed to demote itself from master"
3477 " candidate status: %s" % msg)
3479 _RedistributeAncillaryFiles(self, additional_nodes=[node])
3480 self.context.AddNode(new_node, self.proc.GetECId())
3483 class LUSetNodeParams(LogicalUnit):
3484 """Modifies the parameters of a node.
3487 HPATH = "node-modify"
3488 HTYPE = constants.HTYPE_NODE
3489 _OP_REQP = ["node_name"]
3492 def CheckArguments(self):
3493 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
3494 _CheckBooleanOpField(self.op, 'master_candidate')
3495 _CheckBooleanOpField(self.op, 'offline')
3496 _CheckBooleanOpField(self.op, 'drained')
3497 _CheckBooleanOpField(self.op, 'auto_promote')
3498 all_mods = [self.op.offline, self.op.master_candidate, self.op.drained]
3499 if all_mods.count(None) == 3:
3500 raise errors.OpPrereqError("Please pass at least one modification",
3502 if all_mods.count(True) > 1:
3503 raise errors.OpPrereqError("Can't set the node into more than one"
3504 " state at the same time",
3507 # Boolean value that tells us whether we're offlining or draining the node
3508 self.offline_or_drain = (self.op.offline == True or
3509 self.op.drained == True)
3510 self.deoffline_or_drain = (self.op.offline == False or
3511 self.op.drained == False)
3512 self.might_demote = (self.op.master_candidate == False or
3513 self.offline_or_drain)
3515 self.lock_all = self.op.auto_promote and self.might_demote
3518 def ExpandNames(self):
3520 self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
3522 self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
3524 def BuildHooksEnv(self):
3527 This runs on the master node.
3531 "OP_TARGET": self.op.node_name,
3532 "MASTER_CANDIDATE": str(self.op.master_candidate),
3533 "OFFLINE": str(self.op.offline),
3534 "DRAINED": str(self.op.drained),
3536 nl = [self.cfg.GetMasterNode(),
3540 def CheckPrereq(self):
3541 """Check prerequisites.
3543 This only checks the instance list against the existing names.
3546 node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
3548 if (self.op.master_candidate is not None or
3549 self.op.drained is not None or
3550 self.op.offline is not None):
3551 # we can't change the master's node flags
3552 if self.op.node_name == self.cfg.GetMasterNode():
3553 raise errors.OpPrereqError("The master role can be changed"
3554 " only via masterfailover",
3558 if node.master_candidate and self.might_demote and not self.lock_all:
3559 assert not self.op.auto_promote, "auto-promote set but lock_all not"
3560 # check if after removing the current node, we're missing master
3562 (mc_remaining, mc_should, _) = \
3563 self.cfg.GetMasterCandidateStats(exceptions=[node.name])
3564 if mc_remaining < mc_should:
3565 raise errors.OpPrereqError("Not enough master candidates, please"
3566 " pass auto_promote to allow promotion",
3569 if (self.op.master_candidate == True and
3570 ((node.offline and not self.op.offline == False) or
3571 (node.drained and not self.op.drained == False))):
3572 raise errors.OpPrereqError("Node '%s' is offline or drained, can't set"
3573 " to master_candidate" % node.name,
3576 # If we're being deofflined/drained, we'll MC ourself if needed
3577 if (self.deoffline_or_drain and not self.offline_or_drain and not
3578 self.op.master_candidate == True and not node.master_candidate):
3579 self.op.master_candidate = _DecideSelfPromotion(self)
3580 if self.op.master_candidate:
3581 self.LogInfo("Autopromoting node to master candidate")
3585 def Exec(self, feedback_fn):
3594 if self.op.offline is not None:
3595 node.offline = self.op.offline
3596 result.append(("offline", str(self.op.offline)))
3597 if self.op.offline == True:
3598 if node.master_candidate:
3599 node.master_candidate = False
3601 result.append(("master_candidate", "auto-demotion due to offline"))
3603 node.drained = False
3604 result.append(("drained", "clear drained status due to offline"))
3606 if self.op.master_candidate is not None:
3607 node.master_candidate = self.op.master_candidate
3609 result.append(("master_candidate", str(self.op.master_candidate)))
3610 if self.op.master_candidate == False:
3611 rrc = self.rpc.call_node_demote_from_mc(node.name)
3614 self.LogWarning("Node failed to demote itself: %s" % msg)
3616 if self.op.drained is not None:
3617 node.drained = self.op.drained
3618 result.append(("drained", str(self.op.drained)))
3619 if self.op.drained == True:
3620 if node.master_candidate:
3621 node.master_candidate = False
3623 result.append(("master_candidate", "auto-demotion due to drain"))
3624 rrc = self.rpc.call_node_demote_from_mc(node.name)
3627 self.LogWarning("Node failed to demote itself: %s" % msg)
3629 node.offline = False
3630 result.append(("offline", "clear offline status due to drain"))
3632 # we locked all nodes, we adjust the CP before updating this node
3634 _AdjustCandidatePool(self, [node.name])
3636 # this will trigger configuration file update, if needed
3637 self.cfg.Update(node, feedback_fn)
3639 # this will trigger job queue propagation or cleanup
3641 self.context.ReaddNode(node)
3646 class LUPowercycleNode(NoHooksLU):
3647 """Powercycles a node.
3650 _OP_REQP = ["node_name", "force"]
3653 def CheckArguments(self):
3654 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
3655 if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
3656 raise errors.OpPrereqError("The node is the master and the force"
3657 " parameter was not set",
3660 def ExpandNames(self):
3661 """Locking for PowercycleNode.
3663 This is a last-resort option and shouldn't block on other
3664 jobs. Therefore, we grab no locks.
3667 self.needed_locks = {}
3669 def CheckPrereq(self):
3670 """Check prerequisites.
3672 This LU has no prereqs.
3677 def Exec(self, feedback_fn):
3681 result = self.rpc.call_node_powercycle(self.op.node_name,
3682 self.cfg.GetHypervisorType())
3683 result.Raise("Failed to schedule the reboot")
3684 return result.payload
3687 class LUQueryClusterInfo(NoHooksLU):
3688 """Query cluster configuration.
3694 def ExpandNames(self):
3695 self.needed_locks = {}
3697 def CheckPrereq(self):
3698 """No prerequsites needed for this LU.
3703 def Exec(self, feedback_fn):
3704 """Return cluster config.
3707 cluster = self.cfg.GetClusterInfo()
3710 # Filter just for enabled hypervisors
3711 for os_name, hv_dict in cluster.os_hvp.items():
3712 os_hvp[os_name] = {}
3713 for hv_name, hv_params in hv_dict.items():
3714 if hv_name in cluster.enabled_hypervisors:
3715 os_hvp[os_name][hv_name] = hv_params
3718 "software_version": constants.RELEASE_VERSION,
3719 "protocol_version": constants.PROTOCOL_VERSION,
3720 "config_version": constants.CONFIG_VERSION,
3721 "os_api_version": max(constants.OS_API_VERSIONS),
3722 "export_version": constants.EXPORT_VERSION,
3723 "architecture": (platform.architecture()[0], platform.machine()),
3724 "name": cluster.cluster_name,
3725 "master": cluster.master_node,
3726 "default_hypervisor": cluster.enabled_hypervisors[0],
3727 "enabled_hypervisors": cluster.enabled_hypervisors,
3728 "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
3729 for hypervisor_name in cluster.enabled_hypervisors]),
3731 "beparams": cluster.beparams,
3732 "nicparams": cluster.nicparams,
3733 "candidate_pool_size": cluster.candidate_pool_size,
3734 "master_netdev": cluster.master_netdev,
3735 "volume_group_name": cluster.volume_group_name,
3736 "file_storage_dir": cluster.file_storage_dir,
3737 "maintain_node_health": cluster.maintain_node_health,
3738 "ctime": cluster.ctime,
3739 "mtime": cluster.mtime,
3740 "uuid": cluster.uuid,
3741 "tags": list(cluster.GetTags()),
3742 "uid_pool": cluster.uid_pool,
3748 class LUQueryConfigValues(NoHooksLU):
3749 """Return configuration values.
3754 _FIELDS_DYNAMIC = utils.FieldSet()
3755 _FIELDS_STATIC = utils.FieldSet("cluster_name", "master_node", "drain_flag",
3758 def ExpandNames(self):
3759 self.needed_locks = {}
3761 _CheckOutputFields(static=self._FIELDS_STATIC,
3762 dynamic=self._FIELDS_DYNAMIC,
3763 selected=self.op.output_fields)
3765 def CheckPrereq(self):
3766 """No prerequisites.
3771 def Exec(self, feedback_fn):
3772 """Dump a representation of the cluster config to the standard output.
3776 for field in self.op.output_fields:
3777 if field == "cluster_name":
3778 entry = self.cfg.GetClusterName()
3779 elif field == "master_node":
3780 entry = self.cfg.GetMasterNode()
3781 elif field == "drain_flag":
3782 entry = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
3783 elif field == "watcher_pause":
3784 entry = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
3786 raise errors.ParameterError(field)
3787 values.append(entry)
3791 class LUActivateInstanceDisks(NoHooksLU):
3792 """Bring up an instance's disks.
3795 _OP_REQP = ["instance_name"]
3798 def ExpandNames(self):
3799 self._ExpandAndLockInstance()
3800 self.needed_locks[locking.LEVEL_NODE] = []
3801 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
3803 def DeclareLocks(self, level):
3804 if level == locking.LEVEL_NODE:
3805 self._LockInstancesNodes()
3807 def CheckPrereq(self):
3808 """Check prerequisites.
3810 This checks that the instance is in the cluster.
3813 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3814 assert self.instance is not None, \
3815 "Cannot retrieve locked instance %s" % self.op.instance_name
3816 _CheckNodeOnline(self, self.instance.primary_node)
3817 if not hasattr(self.op, "ignore_size"):
3818 self.op.ignore_size = False
3820 def Exec(self, feedback_fn):
3821 """Activate the disks.
3824 disks_ok, disks_info = \
3825 _AssembleInstanceDisks(self, self.instance,
3826 ignore_size=self.op.ignore_size)
3828 raise errors.OpExecError("Cannot activate block devices")
3833 def _AssembleInstanceDisks(lu, instance, ignore_secondaries=False,
3835 """Prepare the block devices for an instance.
3837 This sets up the block devices on all nodes.
3839 @type lu: L{LogicalUnit}
3840 @param lu: the logical unit on whose behalf we execute
3841 @type instance: L{objects.Instance}
3842 @param instance: the instance for whose disks we assemble
3843 @type ignore_secondaries: boolean
3844 @param ignore_secondaries: if true, errors on secondary nodes
3845 won't result in an error return from the function
3846 @type ignore_size: boolean
3847 @param ignore_size: if true, the current known size of the disk
3848 will not be used during the disk activation, useful for cases
3849 when the size is wrong
3850 @return: False if the operation failed, otherwise a list of
3851 (host, instance_visible_name, node_visible_name)
3852 with the mapping from node devices to instance devices
3857 iname = instance.name
3858 # With the two passes mechanism we try to reduce the window of
3859 # opportunity for the race condition of switching DRBD to primary
3860 # before handshaking occured, but we do not eliminate it
3862 # The proper fix would be to wait (with some limits) until the
3863 # connection has been made and drbd transitions from WFConnection
3864 # into any other network-connected state (Connected, SyncTarget,
3867 # 1st pass, assemble on all nodes in secondary mode
3868 for inst_disk in instance.disks:
3869 for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
3871 node_disk = node_disk.Copy()
3872 node_disk.UnsetSize()
3873 lu.cfg.SetDiskID(node_disk, node)
3874 result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, False)
3875 msg = result.fail_msg
3877 lu.proc.LogWarning("Could not prepare block device %s on node %s"
3878 " (is_primary=False, pass=1): %s",
3879 inst_disk.iv_name, node, msg)
3880 if not ignore_secondaries:
3883 # FIXME: race condition on drbd migration to primary
3885 # 2nd pass, do only the primary node
3886 for inst_disk in instance.disks:
3889 for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
3890 if node != instance.primary_node:
3893 node_disk = node_disk.Copy()
3894 node_disk.UnsetSize()
3895 lu.cfg.SetDiskID(node_disk, node)
3896 result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, True)
3897 msg = result.fail_msg
3899 lu.proc.LogWarning("Could not prepare block device %s on node %s"
3900 " (is_primary=True, pass=2): %s",
3901 inst_disk.iv_name, node, msg)
3904 dev_path = result.payload
3906 device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
3908 # leave the disks configured for the primary node
3909 # this is a workaround that would be fixed better by
3910 # improving the logical/physical id handling
3911 for disk in instance.disks:
3912 lu.cfg.SetDiskID(disk, instance.primary_node)
3914 return disks_ok, device_info
3917 def _StartInstanceDisks(lu, instance, force):
3918 """Start the disks of an instance.
3921 disks_ok, _ = _AssembleInstanceDisks(lu, instance,
3922 ignore_secondaries=force)
3924 _ShutdownInstanceDisks(lu, instance)
3925 if force is not None and not force:
3926 lu.proc.LogWarning("", hint="If the message above refers to a"
3928 " you can retry the operation using '--force'.")
3929 raise errors.OpExecError("Disk consistency error")
3932 class LUDeactivateInstanceDisks(NoHooksLU):
3933 """Shutdown an instance's disks.
3936 _OP_REQP = ["instance_name"]
3939 def ExpandNames(self):
3940 self._ExpandAndLockInstance()
3941 self.needed_locks[locking.LEVEL_NODE] = []
3942 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
3944 def DeclareLocks(self, level):
3945 if level == locking.LEVEL_NODE:
3946 self._LockInstancesNodes()
3948 def CheckPrereq(self):
3949 """Check prerequisites.
3951 This checks that the instance is in the cluster.
3954 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3955 assert self.instance is not None, \
3956 "Cannot retrieve locked instance %s" % self.op.instance_name
3958 def Exec(self, feedback_fn):
3959 """Deactivate the disks
3962 instance = self.instance
3963 _SafeShutdownInstanceDisks(self, instance)
3966 def _SafeShutdownInstanceDisks(lu, instance):
3967 """Shutdown block devices of an instance.
3969 This function checks if an instance is running, before calling
3970 _ShutdownInstanceDisks.
3973 _CheckInstanceDown(lu, instance, "cannot shutdown disks")
3974 _ShutdownInstanceDisks(lu, instance)
3977 def _ShutdownInstanceDisks(lu, instance, ignore_primary=False):
3978 """Shutdown block devices of an instance.
3980 This does the shutdown on all nodes of the instance.
3982 If the ignore_primary is false, errors on the primary node are
3987 for disk in instance.disks:
3988 for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
3989 lu.cfg.SetDiskID(top_disk, node)
3990 result = lu.rpc.call_blockdev_shutdown(node, top_disk)
3991 msg = result.fail_msg
3993 lu.LogWarning("Could not shutdown block device %s on node %s: %s",
3994 disk.iv_name, node, msg)
3995 if not ignore_primary or node != instance.primary_node:
4000 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
4001 """Checks if a node has enough free memory.
4003 This function check if a given node has the needed amount of free
4004 memory. In case the node has less memory or we cannot get the
4005 information from the node, this function raise an OpPrereqError
4008 @type lu: C{LogicalUnit}
4009 @param lu: a logical unit from which we get configuration data
4011 @param node: the node to check
4012 @type reason: C{str}
4013 @param reason: string to use in the error message
4014 @type requested: C{int}
4015 @param requested: the amount of memory in MiB to check for
4016 @type hypervisor_name: C{str}
4017 @param hypervisor_name: the hypervisor to ask for memory stats
4018 @raise errors.OpPrereqError: if the node doesn't have enough memory, or
4019 we cannot check the node
4022 nodeinfo = lu.rpc.call_node_info([node], lu.cfg.GetVGName(), hypervisor_name)
4023 nodeinfo[node].Raise("Can't get data from node %s" % node,
4024 prereq=True, ecode=errors.ECODE_ENVIRON)
4025 free_mem = nodeinfo[node].payload.get('memory_free', None)
4026 if not isinstance(free_mem, int):
4027 raise errors.OpPrereqError("Can't compute free memory on node %s, result"
4028 " was '%s'" % (node, free_mem),
4029 errors.ECODE_ENVIRON)
4030 if requested > free_mem:
4031 raise errors.OpPrereqError("Not enough memory on node %s for %s:"
4032 " needed %s MiB, available %s MiB" %
4033 (node, reason, requested, free_mem),
4037 def _CheckNodesFreeDisk(lu, nodenames, requested):
4038 """Checks if nodes have enough free disk space in the default VG.
4040 This function check if all given nodes have the needed amount of
4041 free disk. In case any node has less disk or we cannot get the
4042 information from the node, this function raise an OpPrereqError
4045 @type lu: C{LogicalUnit}
4046 @param lu: a logical unit from which we get configuration data
4047 @type nodenames: C{list}
4048 @param nodenames: the list of node names to check
4049 @type requested: C{int}
4050 @param requested: the amount of disk in MiB to check for
4051 @raise errors.OpPrereqError: if the node doesn't have enough disk, or
4052 we cannot check the node
4055 nodeinfo = lu.rpc.call_node_info(nodenames, lu.cfg.GetVGName(),
4056 lu.cfg.GetHypervisorType())
4057 for node in nodenames:
4058 info = nodeinfo[node]
4059 info.Raise("Cannot get current information from node %s" % node,
4060 prereq=True, ecode=errors.ECODE_ENVIRON)
4061 vg_free = info.payload.get("vg_free", None)
4062 if not isinstance(vg_free, int):
4063 raise errors.OpPrereqError("Can't compute free disk space on node %s,"
4064 " result was '%s'" % (node, vg_free),
4065 errors.ECODE_ENVIRON)
4066 if requested > vg_free:
4067 raise errors.OpPrereqError("Not enough disk space on target node %s:"
4068 " required %d MiB, available %d MiB" %
4069 (node, requested, vg_free),
4073 class LUStartupInstance(LogicalUnit):
4074 """Starts an instance.
4077 HPATH = "instance-start"
4078 HTYPE = constants.HTYPE_INSTANCE
4079 _OP_REQP = ["instance_name", "force"]
4082 def ExpandNames(self):
4083 self._ExpandAndLockInstance()
4085 def BuildHooksEnv(self):
4088 This runs on master, primary and secondary nodes of the instance.
4092 "FORCE": self.op.force,
4094 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
4095 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4098 def CheckPrereq(self):
4099 """Check prerequisites.
4101 This checks that the instance is in the cluster.
4104 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4105 assert self.instance is not None, \
4106 "Cannot retrieve locked instance %s" % self.op.instance_name
4109 self.beparams = getattr(self.op, "beparams", {})
4111 if not isinstance(self.beparams, dict):
4112 raise errors.OpPrereqError("Invalid beparams passed: %s, expected"
4113 " dict" % (type(self.beparams), ),
4115 # fill the beparams dict
4116 utils.ForceDictType(self.beparams, constants.BES_PARAMETER_TYPES)
4117 self.op.beparams = self.beparams
4120 self.hvparams = getattr(self.op, "hvparams", {})
4122 if not isinstance(self.hvparams, dict):
4123 raise errors.OpPrereqError("Invalid hvparams passed: %s, expected"
4124 " dict" % (type(self.hvparams), ),
4127 # check hypervisor parameter syntax (locally)
4128 cluster = self.cfg.GetClusterInfo()
4129 utils.ForceDictType(self.hvparams, constants.HVS_PARAMETER_TYPES)
4130 filled_hvp = objects.FillDict(cluster.hvparams[instance.hypervisor],
4132 filled_hvp.update(self.hvparams)
4133 hv_type = hypervisor.GetHypervisor(instance.hypervisor)
4134 hv_type.CheckParameterSyntax(filled_hvp)
4135 _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
4136 self.op.hvparams = self.hvparams
4138 _CheckNodeOnline(self, instance.primary_node)
4140 bep = self.cfg.GetClusterInfo().FillBE(instance)
4141 # check bridges existence
4142 _CheckInstanceBridgesExist(self, instance)
4144 remote_info = self.rpc.call_instance_info(instance.primary_node,
4146 instance.hypervisor)
4147 remote_info.Raise("Error checking node %s" % instance.primary_node,
4148 prereq=True, ecode=errors.ECODE_ENVIRON)
4149 if not remote_info.payload: # not running already
4150 _CheckNodeFreeMemory(self, instance.primary_node,
4151 "starting instance %s" % instance.name,
4152 bep[constants.BE_MEMORY], instance.hypervisor)
4154 def Exec(self, feedback_fn):
4155 """Start the instance.
4158 instance = self.instance
4159 force = self.op.force
4161 self.cfg.MarkInstanceUp(instance.name)
4163 node_current = instance.primary_node
4165 _StartInstanceDisks(self, instance, force)
4167 result = self.rpc.call_instance_start(node_current, instance,
4168 self.hvparams, self.beparams)
4169 msg = result.fail_msg
4171 _ShutdownInstanceDisks(self, instance)
4172 raise errors.OpExecError("Could not start instance: %s" % msg)
4175 class LURebootInstance(LogicalUnit):
4176 """Reboot an instance.
4179 HPATH = "instance-reboot"
4180 HTYPE = constants.HTYPE_INSTANCE
4181 _OP_REQP = ["instance_name", "ignore_secondaries", "reboot_type"]
4184 def CheckArguments(self):
4185 """Check the arguments.
4188 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
4189 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4191 def ExpandNames(self):
4192 if self.op.reboot_type not in [constants.INSTANCE_REBOOT_SOFT,
4193 constants.INSTANCE_REBOOT_HARD,
4194 constants.INSTANCE_REBOOT_FULL]:
4195 raise errors.ParameterError("reboot type not in [%s, %s, %s]" %
4196 (constants.INSTANCE_REBOOT_SOFT,
4197 constants.INSTANCE_REBOOT_HARD,
4198 constants.INSTANCE_REBOOT_FULL))
4199 self._ExpandAndLockInstance()
4201 def BuildHooksEnv(self):
4204 This runs on master, primary and secondary nodes of the instance.
4208 "IGNORE_SECONDARIES": self.op.ignore_secondaries,
4209 "REBOOT_TYPE": self.op.reboot_type,
4210 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
4212 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
4213 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4216 def CheckPrereq(self):
4217 """Check prerequisites.
4219 This checks that the instance is in the cluster.
4222 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4223 assert self.instance is not None, \
4224 "Cannot retrieve locked instance %s" % self.op.instance_name
4226 _CheckNodeOnline(self, instance.primary_node)
4228 # check bridges existence
4229 _CheckInstanceBridgesExist(self, instance)
4231 def Exec(self, feedback_fn):
4232 """Reboot the instance.
4235 instance = self.instance
4236 ignore_secondaries = self.op.ignore_secondaries
4237 reboot_type = self.op.reboot_type
4239 node_current = instance.primary_node
4241 if reboot_type in [constants.INSTANCE_REBOOT_SOFT,
4242 constants.INSTANCE_REBOOT_HARD]:
4243 for disk in instance.disks:
4244 self.cfg.SetDiskID(disk, node_current)
4245 result = self.rpc.call_instance_reboot(node_current, instance,
4247 self.shutdown_timeout)
4248 result.Raise("Could not reboot instance")
4250 result = self.rpc.call_instance_shutdown(node_current, instance,
4251 self.shutdown_timeout)
4252 result.Raise("Could not shutdown instance for full reboot")
4253 _ShutdownInstanceDisks(self, instance)
4254 _StartInstanceDisks(self, instance, ignore_secondaries)
4255 result = self.rpc.call_instance_start(node_current, instance, None, None)
4256 msg = result.fail_msg
4258 _ShutdownInstanceDisks(self, instance)
4259 raise errors.OpExecError("Could not start instance for"
4260 " full reboot: %s" % msg)
4262 self.cfg.MarkInstanceUp(instance.name)
4265 class LUShutdownInstance(LogicalUnit):
4266 """Shutdown an instance.
4269 HPATH = "instance-stop"
4270 HTYPE = constants.HTYPE_INSTANCE
4271 _OP_REQP = ["instance_name"]
4274 def CheckArguments(self):
4275 """Check the arguments.
4278 self.timeout = getattr(self.op, "timeout",
4279 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4281 def ExpandNames(self):
4282 self._ExpandAndLockInstance()
4284 def BuildHooksEnv(self):
4287 This runs on master, primary and secondary nodes of the instance.
4290 env = _BuildInstanceHookEnvByObject(self, self.instance)
4291 env["TIMEOUT"] = self.timeout
4292 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4295 def CheckPrereq(self):
4296 """Check prerequisites.
4298 This checks that the instance is in the cluster.
4301 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4302 assert self.instance is not None, \
4303 "Cannot retrieve locked instance %s" % self.op.instance_name
4304 _CheckNodeOnline(self, self.instance.primary_node)
4306 def Exec(self, feedback_fn):
4307 """Shutdown the instance.
4310 instance = self.instance
4311 node_current = instance.primary_node
4312 timeout = self.timeout
4313 self.cfg.MarkInstanceDown(instance.name)
4314 result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
4315 msg = result.fail_msg
4317 self.proc.LogWarning("Could not shutdown instance: %s" % msg)
4319 _ShutdownInstanceDisks(self, instance)
4322 class LUReinstallInstance(LogicalUnit):
4323 """Reinstall an instance.
4326 HPATH = "instance-reinstall"
4327 HTYPE = constants.HTYPE_INSTANCE
4328 _OP_REQP = ["instance_name"]
4331 def ExpandNames(self):
4332 self._ExpandAndLockInstance()
4334 def BuildHooksEnv(self):
4337 This runs on master, primary and secondary nodes of the instance.
4340 env = _BuildInstanceHookEnvByObject(self, self.instance)
4341 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4344 def CheckPrereq(self):
4345 """Check prerequisites.
4347 This checks that the instance is in the cluster and is not running.
4350 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4351 assert instance is not None, \
4352 "Cannot retrieve locked instance %s" % self.op.instance_name
4353 _CheckNodeOnline(self, instance.primary_node)
4355 if instance.disk_template == constants.DT_DISKLESS:
4356 raise errors.OpPrereqError("Instance '%s' has no disks" %
4357 self.op.instance_name,
4359 _CheckInstanceDown(self, instance, "cannot reinstall")
4361 self.op.os_type = getattr(self.op, "os_type", None)
4362 self.op.force_variant = getattr(self.op, "force_variant", False)
4363 if self.op.os_type is not None:
4365 pnode = _ExpandNodeName(self.cfg, instance.primary_node)
4366 _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
4368 self.instance = instance
4370 def Exec(self, feedback_fn):
4371 """Reinstall the instance.
4374 inst = self.instance
4376 if self.op.os_type is not None:
4377 feedback_fn("Changing OS to '%s'..." % self.op.os_type)
4378 inst.os = self.op.os_type
4379 self.cfg.Update(inst, feedback_fn)
4381 _StartInstanceDisks(self, inst, None)
4383 feedback_fn("Running the instance OS create scripts...")
4384 # FIXME: pass debug option from opcode to backend
4385 result = self.rpc.call_instance_os_add(inst.primary_node, inst, True,
4386 self.op.debug_level)
4387 result.Raise("Could not install OS for instance %s on node %s" %
4388 (inst.name, inst.primary_node))
4390 _ShutdownInstanceDisks(self, inst)
4393 class LURecreateInstanceDisks(LogicalUnit):
4394 """Recreate an instance's missing disks.
4397 HPATH = "instance-recreate-disks"
4398 HTYPE = constants.HTYPE_INSTANCE
4399 _OP_REQP = ["instance_name", "disks"]
4402 def CheckArguments(self):
4403 """Check the arguments.
4406 if not isinstance(self.op.disks, list):
4407 raise errors.OpPrereqError("Invalid disks parameter", errors.ECODE_INVAL)
4408 for item in self.op.disks:
4409 if (not isinstance(item, int) or
4411 raise errors.OpPrereqError("Invalid disk specification '%s'" %
4412 str(item), errors.ECODE_INVAL)
4414 def ExpandNames(self):
4415 self._ExpandAndLockInstance()
4417 def BuildHooksEnv(self):
4420 This runs on master, primary and secondary nodes of the instance.
4423 env = _BuildInstanceHookEnvByObject(self, self.instance)
4424 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4427 def CheckPrereq(self):
4428 """Check prerequisites.
4430 This checks that the instance is in the cluster and is not running.
4433 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4434 assert instance is not None, \
4435 "Cannot retrieve locked instance %s" % self.op.instance_name
4436 _CheckNodeOnline(self, instance.primary_node)
4438 if instance.disk_template == constants.DT_DISKLESS:
4439 raise errors.OpPrereqError("Instance '%s' has no disks" %
4440 self.op.instance_name, errors.ECODE_INVAL)
4441 _CheckInstanceDown(self, instance, "cannot recreate disks")
4443 if not self.op.disks:
4444 self.op.disks = range(len(instance.disks))
4446 for idx in self.op.disks:
4447 if idx >= len(instance.disks):
4448 raise errors.OpPrereqError("Invalid disk index passed '%s'" % idx,
4451 self.instance = instance
4453 def Exec(self, feedback_fn):
4454 """Recreate the disks.
4458 for idx, _ in enumerate(self.instance.disks):
4459 if idx not in self.op.disks: # disk idx has not been passed in
4463 _CreateDisks(self, self.instance, to_skip=to_skip)
4466 class LURenameInstance(LogicalUnit):
4467 """Rename an instance.
4470 HPATH = "instance-rename"
4471 HTYPE = constants.HTYPE_INSTANCE
4472 _OP_REQP = ["instance_name", "new_name"]
4474 def BuildHooksEnv(self):
4477 This runs on master, primary and secondary nodes of the instance.
4480 env = _BuildInstanceHookEnvByObject(self, self.instance)
4481 env["INSTANCE_NEW_NAME"] = self.op.new_name
4482 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4485 def CheckPrereq(self):
4486 """Check prerequisites.
4488 This checks that the instance is in the cluster and is not running.
4491 self.op.instance_name = _ExpandInstanceName(self.cfg,
4492 self.op.instance_name)
4493 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4494 assert instance is not None
4495 _CheckNodeOnline(self, instance.primary_node)
4496 _CheckInstanceDown(self, instance, "cannot rename")
4497 self.instance = instance
4499 # new name verification
4500 name_info = utils.GetHostInfo(self.op.new_name)
4502 self.op.new_name = new_name = name_info.name
4503 instance_list = self.cfg.GetInstanceList()
4504 if new_name in instance_list:
4505 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
4506 new_name, errors.ECODE_EXISTS)
4508 if not getattr(self.op, "ignore_ip", False):
4509 if utils.TcpPing(name_info.ip, constants.DEFAULT_NODED_PORT):
4510 raise errors.OpPrereqError("IP %s of instance %s already in use" %
4511 (name_info.ip, new_name),
4512 errors.ECODE_NOTUNIQUE)
4515 def Exec(self, feedback_fn):
4516 """Reinstall the instance.
4519 inst = self.instance
4520 old_name = inst.name
4522 if inst.disk_template == constants.DT_FILE:
4523 old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
4525 self.cfg.RenameInstance(inst.name, self.op.new_name)
4526 # Change the instance lock. This is definitely safe while we hold the BGL
4527 self.context.glm.remove(locking.LEVEL_INSTANCE, old_name)
4528 self.context.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
4530 # re-read the instance from the configuration after rename
4531 inst = self.cfg.GetInstanceInfo(self.op.new_name)
4533 if inst.disk_template == constants.DT_FILE:
4534 new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
4535 result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
4536 old_file_storage_dir,
4537 new_file_storage_dir)
4538 result.Raise("Could not rename on node %s directory '%s' to '%s'"
4539 " (but the instance has been renamed in Ganeti)" %
4540 (inst.primary_node, old_file_storage_dir,
4541 new_file_storage_dir))
4543 _StartInstanceDisks(self, inst, None)
4545 result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
4546 old_name, self.op.debug_level)
4547 msg = result.fail_msg
4549 msg = ("Could not run OS rename script for instance %s on node %s"
4550 " (but the instance has been renamed in Ganeti): %s" %
4551 (inst.name, inst.primary_node, msg))
4552 self.proc.LogWarning(msg)
4554 _ShutdownInstanceDisks(self, inst)
4557 class LURemoveInstance(LogicalUnit):
4558 """Remove an instance.
4561 HPATH = "instance-remove"
4562 HTYPE = constants.HTYPE_INSTANCE
4563 _OP_REQP = ["instance_name", "ignore_failures"]
4566 def CheckArguments(self):
4567 """Check the arguments.
4570 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
4571 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4573 def ExpandNames(self):
4574 self._ExpandAndLockInstance()
4575 self.needed_locks[locking.LEVEL_NODE] = []
4576 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4578 def DeclareLocks(self, level):
4579 if level == locking.LEVEL_NODE:
4580 self._LockInstancesNodes()
4582 def BuildHooksEnv(self):
4585 This runs on master, primary and secondary nodes of the instance.
4588 env = _BuildInstanceHookEnvByObject(self, self.instance)
4589 env["SHUTDOWN_TIMEOUT"] = self.shutdown_timeout
4590 nl = [self.cfg.GetMasterNode()]
4591 nl_post = list(self.instance.all_nodes) + nl
4592 return env, nl, nl_post
4594 def CheckPrereq(self):
4595 """Check prerequisites.
4597 This checks that the instance is in the cluster.
4600 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4601 assert self.instance is not None, \
4602 "Cannot retrieve locked instance %s" % self.op.instance_name
4604 def Exec(self, feedback_fn):
4605 """Remove the instance.
4608 instance = self.instance
4609 logging.info("Shutting down instance %s on node %s",
4610 instance.name, instance.primary_node)
4612 result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
4613 self.shutdown_timeout)
4614 msg = result.fail_msg
4616 if self.op.ignore_failures:
4617 feedback_fn("Warning: can't shutdown instance: %s" % msg)
4619 raise errors.OpExecError("Could not shutdown instance %s on"
4621 (instance.name, instance.primary_node, msg))
4623 logging.info("Removing block devices for instance %s", instance.name)
4625 if not _RemoveDisks(self, instance):
4626 if self.op.ignore_failures:
4627 feedback_fn("Warning: can't remove instance's disks")
4629 raise errors.OpExecError("Can't remove instance's disks")
4631 logging.info("Removing instance %s out of cluster config", instance.name)
4633 self.cfg.RemoveInstance(instance.name)
4634 self.remove_locks[locking.LEVEL_INSTANCE] = instance.name
4637 class LUQueryInstances(NoHooksLU):
4638 """Logical unit for querying instances.
4641 # pylint: disable-msg=W0142
4642 _OP_REQP = ["output_fields", "names", "use_locking"]
4644 _SIMPLE_FIELDS = ["name", "os", "network_port", "hypervisor",
4645 "serial_no", "ctime", "mtime", "uuid"]
4646 _FIELDS_STATIC = utils.FieldSet(*["name", "os", "pnode", "snodes",
4648 "disk_template", "ip", "mac", "bridge",
4649 "nic_mode", "nic_link",
4650 "sda_size", "sdb_size", "vcpus", "tags",
4651 "network_port", "beparams",
4652 r"(disk)\.(size)/([0-9]+)",
4653 r"(disk)\.(sizes)", "disk_usage",
4654 r"(nic)\.(mac|ip|mode|link)/([0-9]+)",
4655 r"(nic)\.(bridge)/([0-9]+)",
4656 r"(nic)\.(macs|ips|modes|links|bridges)",
4657 r"(disk|nic)\.(count)",
4659 ] + _SIMPLE_FIELDS +
4661 for name in constants.HVS_PARAMETERS
4662 if name not in constants.HVC_GLOBALS] +
4664 for name in constants.BES_PARAMETERS])
4665 _FIELDS_DYNAMIC = utils.FieldSet("oper_state", "oper_ram", "status")
4668 def ExpandNames(self):
4669 _CheckOutputFields(static=self._FIELDS_STATIC,
4670 dynamic=self._FIELDS_DYNAMIC,
4671 selected=self.op.output_fields)
4673 self.needed_locks = {}
4674 self.share_locks[locking.LEVEL_INSTANCE] = 1
4675 self.share_locks[locking.LEVEL_NODE] = 1
4678 self.wanted = _GetWantedInstances(self, self.op.names)
4680 self.wanted = locking.ALL_SET
4682 self.do_node_query = self._FIELDS_STATIC.NonMatching(self.op.output_fields)
4683 self.do_locking = self.do_node_query and self.op.use_locking
4685 self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
4686 self.needed_locks[locking.LEVEL_NODE] = []
4687 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4689 def DeclareLocks(self, level):
4690 if level == locking.LEVEL_NODE and self.do_locking:
4691 self._LockInstancesNodes()
4693 def CheckPrereq(self):
4694 """Check prerequisites.
4699 def Exec(self, feedback_fn):
4700 """Computes the list of nodes and their attributes.
4703 # pylint: disable-msg=R0912
4704 # way too many branches here
4705 all_info = self.cfg.GetAllInstancesInfo()
4706 if self.wanted == locking.ALL_SET:
4707 # caller didn't specify instance names, so ordering is not important
4709 instance_names = self.acquired_locks[locking.LEVEL_INSTANCE]
4711 instance_names = all_info.keys()
4712 instance_names = utils.NiceSort(instance_names)
4714 # caller did specify names, so we must keep the ordering
4716 tgt_set = self.acquired_locks[locking.LEVEL_INSTANCE]
4718 tgt_set = all_info.keys()
4719 missing = set(self.wanted).difference(tgt_set)
4721 raise errors.OpExecError("Some instances were removed before"
4722 " retrieving their data: %s" % missing)
4723 instance_names = self.wanted
4725 instance_list = [all_info[iname] for iname in instance_names]
4727 # begin data gathering
4729 nodes = frozenset([inst.primary_node for inst in instance_list])
4730 hv_list = list(set([inst.hypervisor for inst in instance_list]))
4734 if self.do_node_query:
4736 node_data = self.rpc.call_all_instances_info(nodes, hv_list)
4738 result = node_data[name]
4740 # offline nodes will be in both lists
4741 off_nodes.append(name)
4743 bad_nodes.append(name)
4746 live_data.update(result.payload)
4747 # else no instance is alive
4749 live_data = dict([(name, {}) for name in instance_names])
4751 # end data gathering
4756 cluster = self.cfg.GetClusterInfo()
4757 for instance in instance_list:
4759 i_hv = cluster.FillHV(instance, skip_globals=True)
4760 i_be = cluster.FillBE(instance)
4761 i_nicp = [objects.FillDict(cluster.nicparams[constants.PP_DEFAULT],
4762 nic.nicparams) for nic in instance.nics]
4763 for field in self.op.output_fields:
4764 st_match = self._FIELDS_STATIC.Matches(field)
4765 if field in self._SIMPLE_FIELDS:
4766 val = getattr(instance, field)
4767 elif field == "pnode":
4768 val = instance.primary_node
4769 elif field == "snodes":
4770 val = list(instance.secondary_nodes)
4771 elif field == "admin_state":
4772 val = instance.admin_up
4773 elif field == "oper_state":
4774 if instance.primary_node in bad_nodes:
4777 val = bool(live_data.get(instance.name))
4778 elif field == "status":
4779 if instance.primary_node in off_nodes:
4780 val = "ERROR_nodeoffline"
4781 elif instance.primary_node in bad_nodes:
4782 val = "ERROR_nodedown"
4784 running = bool(live_data.get(instance.name))
4786 if instance.admin_up:
4791 if instance.admin_up:
4795 elif field == "oper_ram":
4796 if instance.primary_node in bad_nodes:
4798 elif instance.name in live_data:
4799 val = live_data[instance.name].get("memory", "?")
4802 elif field == "vcpus":
4803 val = i_be[constants.BE_VCPUS]
4804 elif field == "disk_template":
4805 val = instance.disk_template
4808 val = instance.nics[0].ip
4811 elif field == "nic_mode":
4813 val = i_nicp[0][constants.NIC_MODE]
4816 elif field == "nic_link":
4818 val = i_nicp[0][constants.NIC_LINK]
4821 elif field == "bridge":
4822 if (instance.nics and
4823 i_nicp[0][constants.NIC_MODE] == constants.NIC_MODE_BRIDGED):
4824 val = i_nicp[0][constants.NIC_LINK]
4827 elif field == "mac":
4829 val = instance.nics[0].mac
4832 elif field == "sda_size" or field == "sdb_size":
4833 idx = ord(field[2]) - ord('a')
4835 val = instance.FindDisk(idx).size
4836 except errors.OpPrereqError:
4838 elif field == "disk_usage": # total disk usage per node
4839 disk_sizes = [{'size': disk.size} for disk in instance.disks]
4840 val = _ComputeDiskSize(instance.disk_template, disk_sizes)
4841 elif field == "tags":
4842 val = list(instance.GetTags())
4843 elif field == "hvparams":
4845 elif (field.startswith(HVPREFIX) and
4846 field[len(HVPREFIX):] in constants.HVS_PARAMETERS and
4847 field[len(HVPREFIX):] not in constants.HVC_GLOBALS):
4848 val = i_hv.get(field[len(HVPREFIX):], None)
4849 elif field == "beparams":
4851 elif (field.startswith(BEPREFIX) and
4852 field[len(BEPREFIX):] in constants.BES_PARAMETERS):
4853 val = i_be.get(field[len(BEPREFIX):], None)
4854 elif st_match and st_match.groups():
4855 # matches a variable list
4856 st_groups = st_match.groups()
4857 if st_groups and st_groups[0] == "disk":
4858 if st_groups[1] == "count":
4859 val = len(instance.disks)
4860 elif st_groups[1] == "sizes":
4861 val = [disk.size for disk in instance.disks]
4862 elif st_groups[1] == "size":
4864 val = instance.FindDisk(st_groups[2]).size
4865 except errors.OpPrereqError:
4868 assert False, "Unhandled disk parameter"
4869 elif st_groups[0] == "nic":
4870 if st_groups[1] == "count":
4871 val = len(instance.nics)
4872 elif st_groups[1] == "macs":
4873 val = [nic.mac for nic in instance.nics]
4874 elif st_groups[1] == "ips":
4875 val = [nic.ip for nic in instance.nics]
4876 elif st_groups[1] == "modes":
4877 val = [nicp[constants.NIC_MODE] for nicp in i_nicp]
4878 elif st_groups[1] == "links":
4879 val = [nicp[constants.NIC_LINK] for nicp in i_nicp]
4880 elif st_groups[1] == "bridges":
4883 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
4884 val.append(nicp[constants.NIC_LINK])
4889 nic_idx = int(st_groups[2])
4890 if nic_idx >= len(instance.nics):
4893 if st_groups[1] == "mac":
4894 val = instance.nics[nic_idx].mac
4895 elif st_groups[1] == "ip":
4896 val = instance.nics[nic_idx].ip
4897 elif st_groups[1] == "mode":
4898 val = i_nicp[nic_idx][constants.NIC_MODE]
4899 elif st_groups[1] == "link":
4900 val = i_nicp[nic_idx][constants.NIC_LINK]
4901 elif st_groups[1] == "bridge":
4902 nic_mode = i_nicp[nic_idx][constants.NIC_MODE]
4903 if nic_mode == constants.NIC_MODE_BRIDGED:
4904 val = i_nicp[nic_idx][constants.NIC_LINK]
4908 assert False, "Unhandled NIC parameter"
4910 assert False, ("Declared but unhandled variable parameter '%s'" %
4913 assert False, "Declared but unhandled parameter '%s'" % field
4920 class LUFailoverInstance(LogicalUnit):
4921 """Failover an instance.
4924 HPATH = "instance-failover"
4925 HTYPE = constants.HTYPE_INSTANCE
4926 _OP_REQP = ["instance_name", "ignore_consistency"]
4929 def CheckArguments(self):
4930 """Check the arguments.
4933 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
4934 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4936 def ExpandNames(self):
4937 self._ExpandAndLockInstance()
4938 self.needed_locks[locking.LEVEL_NODE] = []
4939 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4941 def DeclareLocks(self, level):
4942 if level == locking.LEVEL_NODE:
4943 self._LockInstancesNodes()
4945 def BuildHooksEnv(self):
4948 This runs on master, primary and secondary nodes of the instance.
4951 instance = self.instance
4952 source_node = instance.primary_node
4953 target_node = instance.secondary_nodes[0]
4955 "IGNORE_CONSISTENCY": self.op.ignore_consistency,
4956 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
4957 "OLD_PRIMARY": source_node,
4958 "OLD_SECONDARY": target_node,
4959 "NEW_PRIMARY": target_node,
4960 "NEW_SECONDARY": source_node,
4962 env.update(_BuildInstanceHookEnvByObject(self, instance))
4963 nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
4965 nl_post.append(source_node)
4966 return env, nl, nl_post
4968 def CheckPrereq(self):
4969 """Check prerequisites.
4971 This checks that the instance is in the cluster.
4974 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4975 assert self.instance is not None, \
4976 "Cannot retrieve locked instance %s" % self.op.instance_name
4978 bep = self.cfg.GetClusterInfo().FillBE(instance)
4979 if instance.disk_template not in constants.DTS_NET_MIRROR:
4980 raise errors.OpPrereqError("Instance's disk layout is not"
4981 " network mirrored, cannot failover.",
4984 secondary_nodes = instance.secondary_nodes
4985 if not secondary_nodes:
4986 raise errors.ProgrammerError("no secondary node but using "
4987 "a mirrored disk template")
4989 target_node = secondary_nodes[0]
4990 _CheckNodeOnline(self, target_node)
4991 _CheckNodeNotDrained(self, target_node)
4992 if instance.admin_up:
4993 # check memory requirements on the secondary node
4994 _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
4995 instance.name, bep[constants.BE_MEMORY],
4996 instance.hypervisor)
4998 self.LogInfo("Not checking memory on the secondary node as"
4999 " instance will not be started")
5001 # check bridge existance
5002 _CheckInstanceBridgesExist(self, instance, node=target_node)
5004 def Exec(self, feedback_fn):
5005 """Failover an instance.
5007 The failover is done by shutting it down on its present node and
5008 starting it on the secondary.
5011 instance = self.instance
5013 source_node = instance.primary_node
5014 target_node = instance.secondary_nodes[0]
5016 if instance.admin_up:
5017 feedback_fn("* checking disk consistency between source and target")
5018 for dev in instance.disks:
5019 # for drbd, these are drbd over lvm
5020 if not _CheckDiskConsistency(self, dev, target_node, False):
5021 if not self.op.ignore_consistency:
5022 raise errors.OpExecError("Disk %s is degraded on target node,"
5023 " aborting failover." % dev.iv_name)
5025 feedback_fn("* not checking disk consistency as instance is not running")
5027 feedback_fn("* shutting down instance on source node")
5028 logging.info("Shutting down instance %s on node %s",
5029 instance.name, source_node)
5031 result = self.rpc.call_instance_shutdown(source_node, instance,
5032 self.shutdown_timeout)
5033 msg = result.fail_msg
5035 if self.op.ignore_consistency:
5036 self.proc.LogWarning("Could not shutdown instance %s on node %s."
5037 " Proceeding anyway. Please make sure node"
5038 " %s is down. Error details: %s",
5039 instance.name, source_node, source_node, msg)
5041 raise errors.OpExecError("Could not shutdown instance %s on"
5043 (instance.name, source_node, msg))
5045 feedback_fn("* deactivating the instance's disks on source node")
5046 if not _ShutdownInstanceDisks(self, instance, ignore_primary=True):
5047 raise errors.OpExecError("Can't shut down the instance's disks.")
5049 instance.primary_node = target_node
5050 # distribute new instance config to the other nodes
5051 self.cfg.Update(instance, feedback_fn)
5053 # Only start the instance if it's marked as up
5054 if instance.admin_up:
5055 feedback_fn("* activating the instance's disks on target node")
5056 logging.info("Starting instance %s on node %s",
5057 instance.name, target_node)
5059 disks_ok, _ = _AssembleInstanceDisks(self, instance,
5060 ignore_secondaries=True)
5062 _ShutdownInstanceDisks(self, instance)
5063 raise errors.OpExecError("Can't activate the instance's disks")
5065 feedback_fn("* starting the instance on the target node")
5066 result = self.rpc.call_instance_start(target_node, instance, None, None)
5067 msg = result.fail_msg
5069 _ShutdownInstanceDisks(self, instance)
5070 raise errors.OpExecError("Could not start instance %s on node %s: %s" %
5071 (instance.name, target_node, msg))
5074 class LUMigrateInstance(LogicalUnit):
5075 """Migrate an instance.
5077 This is migration without shutting down, compared to the failover,
5078 which is done with shutdown.
5081 HPATH = "instance-migrate"
5082 HTYPE = constants.HTYPE_INSTANCE
5083 _OP_REQP = ["instance_name", "live", "cleanup"]
5087 def ExpandNames(self):
5088 self._ExpandAndLockInstance()
5090 self.needed_locks[locking.LEVEL_NODE] = []
5091 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5093 self._migrater = TLMigrateInstance(self, self.op.instance_name,
5094 self.op.live, self.op.cleanup)
5095 self.tasklets = [self._migrater]
5097 def DeclareLocks(self, level):
5098 if level == locking.LEVEL_NODE:
5099 self._LockInstancesNodes()
5101 def BuildHooksEnv(self):
5104 This runs on master, primary and secondary nodes of the instance.
5107 instance = self._migrater.instance
5108 source_node = instance.primary_node
5109 target_node = instance.secondary_nodes[0]
5110 env = _BuildInstanceHookEnvByObject(self, instance)
5111 env["MIGRATE_LIVE"] = self.op.live
5112 env["MIGRATE_CLEANUP"] = self.op.cleanup
5114 "OLD_PRIMARY": source_node,
5115 "OLD_SECONDARY": target_node,
5116 "NEW_PRIMARY": target_node,
5117 "NEW_SECONDARY": source_node,
5119 nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
5121 nl_post.append(source_node)
5122 return env, nl, nl_post
5125 class LUMoveInstance(LogicalUnit):
5126 """Move an instance by data-copying.
5129 HPATH = "instance-move"
5130 HTYPE = constants.HTYPE_INSTANCE
5131 _OP_REQP = ["instance_name", "target_node"]
5134 def CheckArguments(self):
5135 """Check the arguments.
5138 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
5139 constants.DEFAULT_SHUTDOWN_TIMEOUT)
5141 def ExpandNames(self):
5142 self._ExpandAndLockInstance()
5143 target_node = _ExpandNodeName(self.cfg, self.op.target_node)
5144 self.op.target_node = target_node
5145 self.needed_locks[locking.LEVEL_NODE] = [target_node]
5146 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
5148 def DeclareLocks(self, level):
5149 if level == locking.LEVEL_NODE:
5150 self._LockInstancesNodes(primary_only=True)
5152 def BuildHooksEnv(self):
5155 This runs on master, primary and secondary nodes of the instance.
5159 "TARGET_NODE": self.op.target_node,
5160 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
5162 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
5163 nl = [self.cfg.GetMasterNode()] + [self.instance.primary_node,
5164 self.op.target_node]
5167 def CheckPrereq(self):
5168 """Check prerequisites.
5170 This checks that the instance is in the cluster.
5173 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5174 assert self.instance is not None, \
5175 "Cannot retrieve locked instance %s" % self.op.instance_name
5177 node = self.cfg.GetNodeInfo(self.op.target_node)
5178 assert node is not None, \
5179 "Cannot retrieve locked node %s" % self.op.target_node
5181 self.target_node = target_node = node.name
5183 if target_node == instance.primary_node:
5184 raise errors.OpPrereqError("Instance %s is already on the node %s" %
5185 (instance.name, target_node),
5188 bep = self.cfg.GetClusterInfo().FillBE(instance)
5190 for idx, dsk in enumerate(instance.disks):
5191 if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
5192 raise errors.OpPrereqError("Instance disk %d has a complex layout,"
5193 " cannot copy" % idx, errors.ECODE_STATE)
5195 _CheckNodeOnline(self, target_node)
5196 _CheckNodeNotDrained(self, target_node)
5198 if instance.admin_up:
5199 # check memory requirements on the secondary node
5200 _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
5201 instance.name, bep[constants.BE_MEMORY],
5202 instance.hypervisor)
5204 self.LogInfo("Not checking memory on the secondary node as"
5205 " instance will not be started")
5207 # check bridge existance
5208 _CheckInstanceBridgesExist(self, instance, node=target_node)
5210 def Exec(self, feedback_fn):
5211 """Move an instance.
5213 The move is done by shutting it down on its present node, copying
5214 the data over (slow) and starting it on the new node.
5217 instance = self.instance
5219 source_node = instance.primary_node
5220 target_node = self.target_node
5222 self.LogInfo("Shutting down instance %s on source node %s",
5223 instance.name, source_node)
5225 result = self.rpc.call_instance_shutdown(source_node, instance,
5226 self.shutdown_timeout)
5227 msg = result.fail_msg
5229 if self.op.ignore_consistency:
5230 self.proc.LogWarning("Could not shutdown instance %s on node %s."
5231 " Proceeding anyway. Please make sure node"
5232 " %s is down. Error details: %s",
5233 instance.name, source_node, source_node, msg)
5235 raise errors.OpExecError("Could not shutdown instance %s on"
5237 (instance.name, source_node, msg))
5239 # create the target disks
5241 _CreateDisks(self, instance, target_node=target_node)
5242 except errors.OpExecError:
5243 self.LogWarning("Device creation failed, reverting...")
5245 _RemoveDisks(self, instance, target_node=target_node)
5247 self.cfg.ReleaseDRBDMinors(instance.name)
5250 cluster_name = self.cfg.GetClusterInfo().cluster_name
5253 # activate, get path, copy the data over
5254 for idx, disk in enumerate(instance.disks):
5255 self.LogInfo("Copying data for disk %d", idx)
5256 result = self.rpc.call_blockdev_assemble(target_node, disk,
5257 instance.name, True)
5259 self.LogWarning("Can't assemble newly created disk %d: %s",
5260 idx, result.fail_msg)
5261 errs.append(result.fail_msg)
5263 dev_path = result.payload
5264 result = self.rpc.call_blockdev_export(source_node, disk,
5265 target_node, dev_path,
5268 self.LogWarning("Can't copy data over for disk %d: %s",
5269 idx, result.fail_msg)
5270 errs.append(result.fail_msg)
5274 self.LogWarning("Some disks failed to copy, aborting")
5276 _RemoveDisks(self, instance, target_node=target_node)
5278 self.cfg.ReleaseDRBDMinors(instance.name)
5279 raise errors.OpExecError("Errors during disk copy: %s" %
5282 instance.primary_node = target_node
5283 self.cfg.Update(instance, feedback_fn)
5285 self.LogInfo("Removing the disks on the original node")
5286 _RemoveDisks(self, instance, target_node=source_node)
5288 # Only start the instance if it's marked as up
5289 if instance.admin_up:
5290 self.LogInfo("Starting instance %s on node %s",
5291 instance.name, target_node)
5293 disks_ok, _ = _AssembleInstanceDisks(self, instance,
5294 ignore_secondaries=True)
5296 _ShutdownInstanceDisks(self, instance)
5297 raise errors.OpExecError("Can't activate the instance's disks")
5299 result = self.rpc.call_instance_start(target_node, instance, None, None)
5300 msg = result.fail_msg
5302 _ShutdownInstanceDisks(self, instance)
5303 raise errors.OpExecError("Could not start instance %s on node %s: %s" %
5304 (instance.name, target_node, msg))
5307 class LUMigrateNode(LogicalUnit):
5308 """Migrate all instances from a node.
5311 HPATH = "node-migrate"
5312 HTYPE = constants.HTYPE_NODE
5313 _OP_REQP = ["node_name", "live"]
5316 def ExpandNames(self):
5317 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5319 self.needed_locks = {
5320 locking.LEVEL_NODE: [self.op.node_name],
5323 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
5325 # Create tasklets for migrating instances for all instances on this node
5329 for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name):
5330 logging.debug("Migrating instance %s", inst.name)
5331 names.append(inst.name)
5333 tasklets.append(TLMigrateInstance(self, inst.name, self.op.live, False))
5335 self.tasklets = tasklets
5337 # Declare instance locks
5338 self.needed_locks[locking.LEVEL_INSTANCE] = names
5340 def DeclareLocks(self, level):
5341 if level == locking.LEVEL_NODE:
5342 self._LockInstancesNodes()
5344 def BuildHooksEnv(self):
5347 This runs on the master, the primary and all the secondaries.
5351 "NODE_NAME": self.op.node_name,
5354 nl = [self.cfg.GetMasterNode()]
5356 return (env, nl, nl)
5359 class TLMigrateInstance(Tasklet):
5360 def __init__(self, lu, instance_name, live, cleanup):
5361 """Initializes this class.
5364 Tasklet.__init__(self, lu)
5367 self.instance_name = instance_name
5369 self.cleanup = cleanup
5371 def CheckPrereq(self):
5372 """Check prerequisites.
5374 This checks that the instance is in the cluster.
5377 instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
5378 instance = self.cfg.GetInstanceInfo(instance_name)
5379 assert instance is not None
5381 if instance.disk_template != constants.DT_DRBD8:
5382 raise errors.OpPrereqError("Instance's disk layout is not"
5383 " drbd8, cannot migrate.", errors.ECODE_STATE)
5385 secondary_nodes = instance.secondary_nodes
5386 if not secondary_nodes:
5387 raise errors.ConfigurationError("No secondary node but using"
5388 " drbd8 disk template")
5390 i_be = self.cfg.GetClusterInfo().FillBE(instance)
5392 target_node = secondary_nodes[0]
5393 # check memory requirements on the secondary node
5394 _CheckNodeFreeMemory(self, target_node, "migrating instance %s" %
5395 instance.name, i_be[constants.BE_MEMORY],
5396 instance.hypervisor)
5398 # check bridge existance
5399 _CheckInstanceBridgesExist(self, instance, node=target_node)
5401 if not self.cleanup:
5402 _CheckNodeNotDrained(self, target_node)
5403 result = self.rpc.call_instance_migratable(instance.primary_node,
5405 result.Raise("Can't migrate, please use failover",
5406 prereq=True, ecode=errors.ECODE_STATE)
5408 self.instance = instance
5410 def _WaitUntilSync(self):
5411 """Poll with custom rpc for disk sync.
5413 This uses our own step-based rpc call.
5416 self.feedback_fn("* wait until resync is done")
5420 result = self.rpc.call_drbd_wait_sync(self.all_nodes,
5422 self.instance.disks)
5424 for node, nres in result.items():
5425 nres.Raise("Cannot resync disks on node %s" % node)
5426 node_done, node_percent = nres.payload
5427 all_done = all_done and node_done
5428 if node_percent is not None:
5429 min_percent = min(min_percent, node_percent)
5431 if min_percent < 100:
5432 self.feedback_fn(" - progress: %.1f%%" % min_percent)
5435 def _EnsureSecondary(self, node):
5436 """Demote a node to secondary.
5439 self.feedback_fn("* switching node %s to secondary mode" % node)
5441 for dev in self.instance.disks:
5442 self.cfg.SetDiskID(dev, node)
5444 result = self.rpc.call_blockdev_close(node, self.instance.name,
5445 self.instance.disks)
5446 result.Raise("Cannot change disk to secondary on node %s" % node)
5448 def _GoStandalone(self):
5449 """Disconnect from the network.
5452 self.feedback_fn("* changing into standalone mode")
5453 result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
5454 self.instance.disks)
5455 for node, nres in result.items():
5456 nres.Raise("Cannot disconnect disks node %s" % node)
5458 def _GoReconnect(self, multimaster):
5459 """Reconnect to the network.
5465 msg = "single-master"
5466 self.feedback_fn("* changing disks into %s mode" % msg)
5467 result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
5468 self.instance.disks,
5469 self.instance.name, multimaster)
5470 for node, nres in result.items():
5471 nres.Raise("Cannot change disks config on node %s" % node)
5473 def _ExecCleanup(self):
5474 """Try to cleanup after a failed migration.
5476 The cleanup is done by:
5477 - check that the instance is running only on one node
5478 (and update the config if needed)
5479 - change disks on its secondary node to secondary
5480 - wait until disks are fully synchronized
5481 - disconnect from the network
5482 - change disks into single-master mode
5483 - wait again until disks are fully synchronized
5486 instance = self.instance
5487 target_node = self.target_node
5488 source_node = self.source_node
5490 # check running on only one node
5491 self.feedback_fn("* checking where the instance actually runs"
5492 " (if this hangs, the hypervisor might be in"
5494 ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
5495 for node, result in ins_l.items():
5496 result.Raise("Can't contact node %s" % node)
5498 runningon_source = instance.name in ins_l[source_node].payload
5499 runningon_target = instance.name in ins_l[target_node].payload
5501 if runningon_source and runningon_target:
5502 raise errors.OpExecError("Instance seems to be running on two nodes,"
5503 " or the hypervisor is confused. You will have"
5504 " to ensure manually that it runs only on one"
5505 " and restart this operation.")
5507 if not (runningon_source or runningon_target):
5508 raise errors.OpExecError("Instance does not seem to be running at all."
5509 " In this case, it's safer to repair by"
5510 " running 'gnt-instance stop' to ensure disk"
5511 " shutdown, and then restarting it.")
5513 if runningon_target:
5514 # the migration has actually succeeded, we need to update the config
5515 self.feedback_fn("* instance running on secondary node (%s),"
5516 " updating config" % target_node)
5517 instance.primary_node = target_node
5518 self.cfg.Update(instance, self.feedback_fn)
5519 demoted_node = source_node
5521 self.feedback_fn("* instance confirmed to be running on its"
5522 " primary node (%s)" % source_node)
5523 demoted_node = target_node
5525 self._EnsureSecondary(demoted_node)
5527 self._WaitUntilSync()
5528 except errors.OpExecError:
5529 # we ignore here errors, since if the device is standalone, it
5530 # won't be able to sync
5532 self._GoStandalone()
5533 self._GoReconnect(False)
5534 self._WaitUntilSync()
5536 self.feedback_fn("* done")
5538 def _RevertDiskStatus(self):
5539 """Try to revert the disk status after a failed migration.
5542 target_node = self.target_node
5544 self._EnsureSecondary(target_node)
5545 self._GoStandalone()
5546 self._GoReconnect(False)
5547 self._WaitUntilSync()
5548 except errors.OpExecError, err:
5549 self.lu.LogWarning("Migration failed and I can't reconnect the"
5550 " drives: error '%s'\n"
5551 "Please look and recover the instance status" %
5554 def _AbortMigration(self):
5555 """Call the hypervisor code to abort a started migration.
5558 instance = self.instance
5559 target_node = self.target_node
5560 migration_info = self.migration_info
5562 abort_result = self.rpc.call_finalize_migration(target_node,
5566 abort_msg = abort_result.fail_msg
5568 logging.error("Aborting migration failed on target node %s: %s",
5569 target_node, abort_msg)
5570 # Don't raise an exception here, as we stil have to try to revert the
5571 # disk status, even if this step failed.
5573 def _ExecMigration(self):
5574 """Migrate an instance.
5576 The migrate is done by:
5577 - change the disks into dual-master mode
5578 - wait until disks are fully synchronized again
5579 - migrate the instance
5580 - change disks on the new secondary node (the old primary) to secondary
5581 - wait until disks are fully synchronized
5582 - change disks into single-master mode
5585 instance = self.instance
5586 target_node = self.target_node
5587 source_node = self.source_node
5589 self.feedback_fn("* checking disk consistency between source and target")
5590 for dev in instance.disks:
5591 if not _CheckDiskConsistency(self, dev, target_node, False):
5592 raise errors.OpExecError("Disk %s is degraded or not fully"
5593 " synchronized on target node,"
5594 " aborting migrate." % dev.iv_name)
5596 # First get the migration information from the remote node
5597 result = self.rpc.call_migration_info(source_node, instance)
5598 msg = result.fail_msg
5600 log_err = ("Failed fetching source migration information from %s: %s" %
5602 logging.error(log_err)
5603 raise errors.OpExecError(log_err)
5605 self.migration_info = migration_info = result.payload
5607 # Then switch the disks to master/master mode
5608 self._EnsureSecondary(target_node)
5609 self._GoStandalone()
5610 self._GoReconnect(True)
5611 self._WaitUntilSync()
5613 self.feedback_fn("* preparing %s to accept the instance" % target_node)
5614 result = self.rpc.call_accept_instance(target_node,
5617 self.nodes_ip[target_node])
5619 msg = result.fail_msg
5621 logging.error("Instance pre-migration failed, trying to revert"
5622 " disk status: %s", msg)
5623 self.feedback_fn("Pre-migration failed, aborting")
5624 self._AbortMigration()
5625 self._RevertDiskStatus()
5626 raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
5627 (instance.name, msg))
5629 self.feedback_fn("* migrating instance to %s" % target_node)
5631 result = self.rpc.call_instance_migrate(source_node, instance,
5632 self.nodes_ip[target_node],
5634 msg = result.fail_msg
5636 logging.error("Instance migration failed, trying to revert"
5637 " disk status: %s", msg)
5638 self.feedback_fn("Migration failed, aborting")
5639 self._AbortMigration()
5640 self._RevertDiskStatus()
5641 raise errors.OpExecError("Could not migrate instance %s: %s" %
5642 (instance.name, msg))
5645 instance.primary_node = target_node
5646 # distribute new instance config to the other nodes
5647 self.cfg.Update(instance, self.feedback_fn)
5649 result = self.rpc.call_finalize_migration(target_node,
5653 msg = result.fail_msg
5655 logging.error("Instance migration succeeded, but finalization failed:"
5657 raise errors.OpExecError("Could not finalize instance migration: %s" %
5660 self._EnsureSecondary(source_node)
5661 self._WaitUntilSync()
5662 self._GoStandalone()
5663 self._GoReconnect(False)
5664 self._WaitUntilSync()
5666 self.feedback_fn("* done")
5668 def Exec(self, feedback_fn):
5669 """Perform the migration.
5672 feedback_fn("Migrating instance %s" % self.instance.name)
5674 self.feedback_fn = feedback_fn
5676 self.source_node = self.instance.primary_node
5677 self.target_node = self.instance.secondary_nodes[0]
5678 self.all_nodes = [self.source_node, self.target_node]
5680 self.source_node: self.cfg.GetNodeInfo(self.source_node).secondary_ip,
5681 self.target_node: self.cfg.GetNodeInfo(self.target_node).secondary_ip,
5685 return self._ExecCleanup()
5687 return self._ExecMigration()
5690 def _CreateBlockDev(lu, node, instance, device, force_create,
5692 """Create a tree of block devices on a given node.
5694 If this device type has to be created on secondaries, create it and
5697 If not, just recurse to children keeping the same 'force' value.
5699 @param lu: the lu on whose behalf we execute
5700 @param node: the node on which to create the device
5701 @type instance: L{objects.Instance}
5702 @param instance: the instance which owns the device
5703 @type device: L{objects.Disk}
5704 @param device: the device to create
5705 @type force_create: boolean
5706 @param force_create: whether to force creation of this device; this
5707 will be change to True whenever we find a device which has
5708 CreateOnSecondary() attribute
5709 @param info: the extra 'metadata' we should attach to the device
5710 (this will be represented as a LVM tag)
5711 @type force_open: boolean
5712 @param force_open: this parameter will be passes to the
5713 L{backend.BlockdevCreate} function where it specifies
5714 whether we run on primary or not, and it affects both
5715 the child assembly and the device own Open() execution
5718 if device.CreateOnSecondary():
5722 for child in device.children:
5723 _CreateBlockDev(lu, node, instance, child, force_create,
5726 if not force_create:
5729 _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
5732 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
5733 """Create a single block device on a given node.
5735 This will not recurse over children of the device, so they must be
5738 @param lu: the lu on whose behalf we execute
5739 @param node: the node on which to create the device
5740 @type instance: L{objects.Instance}
5741 @param instance: the instance which owns the device
5742 @type device: L{objects.Disk}
5743 @param device: the device to create
5744 @param info: the extra 'metadata' we should attach to the device
5745 (this will be represented as a LVM tag)
5746 @type force_open: boolean
5747 @param force_open: this parameter will be passes to the
5748 L{backend.BlockdevCreate} function where it specifies
5749 whether we run on primary or not, and it affects both
5750 the child assembly and the device own Open() execution
5753 lu.cfg.SetDiskID(device, node)
5754 result = lu.rpc.call_blockdev_create(node, device, device.size,
5755 instance.name, force_open, info)
5756 result.Raise("Can't create block device %s on"
5757 " node %s for instance %s" % (device, node, instance.name))
5758 if device.physical_id is None:
5759 device.physical_id = result.payload
5762 def _GenerateUniqueNames(lu, exts):
5763 """Generate a suitable LV name.
5765 This will generate a logical volume name for the given instance.
5770 new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
5771 results.append("%s%s" % (new_id, val))
5775 def _GenerateDRBD8Branch(lu, primary, secondary, size, names, iv_name,
5777 """Generate a drbd8 device complete with its children.
5780 port = lu.cfg.AllocatePort()
5781 vgname = lu.cfg.GetVGName()
5782 shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
5783 dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
5784 logical_id=(vgname, names[0]))
5785 dev_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
5786 logical_id=(vgname, names[1]))
5787 drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
5788 logical_id=(primary, secondary, port,
5791 children=[dev_data, dev_meta],
5796 def _GenerateDiskTemplate(lu, template_name,
5797 instance_name, primary_node,
5798 secondary_nodes, disk_info,
5799 file_storage_dir, file_driver,
5801 """Generate the entire disk layout for a given template type.
5804 #TODO: compute space requirements
5806 vgname = lu.cfg.GetVGName()
5807 disk_count = len(disk_info)
5809 if template_name == constants.DT_DISKLESS:
5811 elif template_name == constants.DT_PLAIN:
5812 if len(secondary_nodes) != 0:
5813 raise errors.ProgrammerError("Wrong template configuration")
5815 names = _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
5816 for i in range(disk_count)])
5817 for idx, disk in enumerate(disk_info):
5818 disk_index = idx + base_index
5819 disk_dev = objects.Disk(dev_type=constants.LD_LV, size=disk["size"],
5820 logical_id=(vgname, names[idx]),
5821 iv_name="disk/%d" % disk_index,
5823 disks.append(disk_dev)
5824 elif template_name == constants.DT_DRBD8:
5825 if len(secondary_nodes) != 1:
5826 raise errors.ProgrammerError("Wrong template configuration")
5827 remote_node = secondary_nodes[0]
5828 minors = lu.cfg.AllocateDRBDMinor(
5829 [primary_node, remote_node] * len(disk_info), instance_name)
5832 for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
5833 for i in range(disk_count)]):
5834 names.append(lv_prefix + "_data")
5835 names.append(lv_prefix + "_meta")
5836 for idx, disk in enumerate(disk_info):
5837 disk_index = idx + base_index
5838 disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
5839 disk["size"], names[idx*2:idx*2+2],
5840 "disk/%d" % disk_index,
5841 minors[idx*2], minors[idx*2+1])
5842 disk_dev.mode = disk["mode"]
5843 disks.append(disk_dev)
5844 elif template_name == constants.DT_FILE:
5845 if len(secondary_nodes) != 0:
5846 raise errors.ProgrammerError("Wrong template configuration")
5848 _RequireFileStorage()
5850 for idx, disk in enumerate(disk_info):
5851 disk_index = idx + base_index
5852 disk_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk["size"],
5853 iv_name="disk/%d" % disk_index,
5854 logical_id=(file_driver,
5855 "%s/disk%d" % (file_storage_dir,
5858 disks.append(disk_dev)
5860 raise errors.ProgrammerError("Invalid disk template '%s'" % template_name)
5864 def _GetInstanceInfoText(instance):
5865 """Compute that text that should be added to the disk's metadata.
5868 return "originstname+%s" % instance.name
5871 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
5872 """Create all disks for an instance.
5874 This abstracts away some work from AddInstance.
5876 @type lu: L{LogicalUnit}
5877 @param lu: the logical unit on whose behalf we execute
5878 @type instance: L{objects.Instance}
5879 @param instance: the instance whose disks we should create
5881 @param to_skip: list of indices to skip
5882 @type target_node: string
5883 @param target_node: if passed, overrides the target node for creation
5885 @return: the success of the creation
5888 info = _GetInstanceInfoText(instance)
5889 if target_node is None:
5890 pnode = instance.primary_node
5891 all_nodes = instance.all_nodes
5896 if instance.disk_template == constants.DT_FILE:
5897 file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
5898 result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
5900 result.Raise("Failed to create directory '%s' on"
5901 " node %s" % (file_storage_dir, pnode))
5903 # Note: this needs to be kept in sync with adding of disks in
5904 # LUSetInstanceParams
5905 for idx, device in enumerate(instance.disks):
5906 if to_skip and idx in to_skip:
5908 logging.info("Creating volume %s for instance %s",
5909 device.iv_name, instance.name)
5911 for node in all_nodes:
5912 f_create = node == pnode
5913 _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
5916 def _RemoveDisks(lu, instance, target_node=None):
5917 """Remove all disks for an instance.
5919 This abstracts away some work from `AddInstance()` and
5920 `RemoveInstance()`. Note that in case some of the devices couldn't
5921 be removed, the removal will continue with the other ones (compare
5922 with `_CreateDisks()`).
5924 @type lu: L{LogicalUnit}
5925 @param lu: the logical unit on whose behalf we execute
5926 @type instance: L{objects.Instance}
5927 @param instance: the instance whose disks we should remove
5928 @type target_node: string
5929 @param target_node: used to override the node on which to remove the disks
5931 @return: the success of the removal
5934 logging.info("Removing block devices for instance %s", instance.name)
5937 for device in instance.disks:
5939 edata = [(target_node, device)]
5941 edata = device.ComputeNodeTree(instance.primary_node)
5942 for node, disk in edata:
5943 lu.cfg.SetDiskID(disk, node)
5944 msg = lu.rpc.call_blockdev_remove(node, disk).fail_msg
5946 lu.LogWarning("Could not remove block device %s on node %s,"
5947 " continuing anyway: %s", device.iv_name, node, msg)
5950 if instance.disk_template == constants.DT_FILE:
5951 file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
5955 tgt = instance.primary_node
5956 result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
5958 lu.LogWarning("Could not remove directory '%s' on node %s: %s",
5959 file_storage_dir, instance.primary_node, result.fail_msg)
5965 def _ComputeDiskSize(disk_template, disks):
5966 """Compute disk size requirements in the volume group
5969 # Required free disk space as a function of disk and swap space
5971 constants.DT_DISKLESS: None,
5972 constants.DT_PLAIN: sum(d["size"] for d in disks),
5973 # 128 MB are added for drbd metadata for each disk
5974 constants.DT_DRBD8: sum(d["size"] + 128 for d in disks),
5975 constants.DT_FILE: None,
5978 if disk_template not in req_size_dict:
5979 raise errors.ProgrammerError("Disk template '%s' size requirement"
5980 " is unknown" % disk_template)
5982 return req_size_dict[disk_template]
5985 def _CheckHVParams(lu, nodenames, hvname, hvparams):
5986 """Hypervisor parameter validation.
5988 This function abstract the hypervisor parameter validation to be
5989 used in both instance create and instance modify.
5991 @type lu: L{LogicalUnit}
5992 @param lu: the logical unit for which we check
5993 @type nodenames: list
5994 @param nodenames: the list of nodes on which we should check
5995 @type hvname: string
5996 @param hvname: the name of the hypervisor we should use
5997 @type hvparams: dict
5998 @param hvparams: the parameters which we need to check
5999 @raise errors.OpPrereqError: if the parameters are not valid
6002 hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames,
6005 for node in nodenames:
6009 info.Raise("Hypervisor parameter validation failed on node %s" % node)
6012 class LUCreateInstance(LogicalUnit):
6013 """Create an instance.
6016 HPATH = "instance-add"
6017 HTYPE = constants.HTYPE_INSTANCE
6018 _OP_REQP = ["instance_name", "disks",
6020 "wait_for_sync", "ip_check", "nics",
6021 "hvparams", "beparams"]
6024 def CheckArguments(self):
6028 # set optional parameters to none if they don't exist
6029 for attr in ["pnode", "snode", "iallocator", "hypervisor",
6030 "disk_template", "identify_defaults"]:
6031 if not hasattr(self.op, attr):
6032 setattr(self.op, attr, None)
6034 # do not require name_check to ease forward/backward compatibility
6036 if not hasattr(self.op, "name_check"):
6037 self.op.name_check = True
6038 if not hasattr(self.op, "no_install"):
6039 self.op.no_install = False
6040 if self.op.no_install and self.op.start:
6041 self.LogInfo("No-installation mode selected, disabling startup")
6042 self.op.start = False
6043 # validate/normalize the instance name
6044 self.op.instance_name = utils.HostInfo.NormalizeName(self.op.instance_name)
6045 if self.op.ip_check and not self.op.name_check:
6046 # TODO: make the ip check more flexible and not depend on the name check
6047 raise errors.OpPrereqError("Cannot do ip checks without a name check",
6049 # check disk information: either all adopt, or no adopt
6050 has_adopt = has_no_adopt = False
6051 for disk in self.op.disks:
6056 if has_adopt and has_no_adopt:
6057 raise errors.OpPrereqError("Either all disks are adopted or none is",
6060 if self.op.disk_template != constants.DT_PLAIN:
6061 raise errors.OpPrereqError("Disk adoption is only supported for the"
6062 " 'plain' disk template",
6064 if self.op.iallocator is not None:
6065 raise errors.OpPrereqError("Disk adoption not allowed with an"
6066 " iallocator script", errors.ECODE_INVAL)
6067 if self.op.mode == constants.INSTANCE_IMPORT:
6068 raise errors.OpPrereqError("Disk adoption not allowed for"
6069 " instance import", errors.ECODE_INVAL)
6071 self.adopt_disks = has_adopt
6073 # verify creation mode
6074 if self.op.mode not in (constants.INSTANCE_CREATE,
6075 constants.INSTANCE_IMPORT):
6076 raise errors.OpPrereqError("Invalid instance creation mode '%s'" %
6077 self.op.mode, errors.ECODE_INVAL)
6079 # instance name verification
6080 if self.op.name_check:
6081 self.hostname1 = utils.GetHostInfo(self.op.instance_name)
6082 self.op.instance_name = self.hostname1.name
6083 # used in CheckPrereq for ip ping check
6084 self.check_ip = self.hostname1.ip
6086 self.check_ip = None
6088 # file storage checks
6089 if (self.op.file_driver and
6090 not self.op.file_driver in constants.FILE_DRIVER):
6091 raise errors.OpPrereqError("Invalid file driver name '%s'" %
6092 self.op.file_driver, errors.ECODE_INVAL)
6094 if self.op.file_storage_dir and os.path.isabs(self.op.file_storage_dir):
6095 raise errors.OpPrereqError("File storage directory path not absolute",
6098 ### Node/iallocator related checks
6099 if [self.op.iallocator, self.op.pnode].count(None) != 1:
6100 raise errors.OpPrereqError("One and only one of iallocator and primary"
6101 " node must be given",
6104 if self.op.mode == constants.INSTANCE_IMPORT:
6105 # On import force_variant must be True, because if we forced it at
6106 # initial install, our only chance when importing it back is that it
6108 self.op.force_variant = True
6110 if self.op.no_install:
6111 self.LogInfo("No-installation mode has no effect during import")
6113 else: # INSTANCE_CREATE
6114 if getattr(self.op, "os_type", None) is None:
6115 raise errors.OpPrereqError("No guest OS specified",
6117 self.op.force_variant = getattr(self.op, "force_variant", False)
6118 if self.op.disk_template is None:
6119 raise errors.OpPrereqError("No disk template specified",
6122 def ExpandNames(self):
6123 """ExpandNames for CreateInstance.
6125 Figure out the right locks for instance creation.
6128 self.needed_locks = {}
6130 instance_name = self.op.instance_name
6131 # this is just a preventive check, but someone might still add this
6132 # instance in the meantime, and creation will fail at lock-add time
6133 if instance_name in self.cfg.GetInstanceList():
6134 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
6135 instance_name, errors.ECODE_EXISTS)
6137 self.add_locks[locking.LEVEL_INSTANCE] = instance_name
6139 if self.op.iallocator:
6140 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6142 self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
6143 nodelist = [self.op.pnode]
6144 if self.op.snode is not None:
6145 self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
6146 nodelist.append(self.op.snode)
6147 self.needed_locks[locking.LEVEL_NODE] = nodelist
6149 # in case of import lock the source node too
6150 if self.op.mode == constants.INSTANCE_IMPORT:
6151 src_node = getattr(self.op, "src_node", None)
6152 src_path = getattr(self.op, "src_path", None)
6154 if src_path is None:
6155 self.op.src_path = src_path = self.op.instance_name
6157 if src_node is None:
6158 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6159 self.op.src_node = None
6160 if os.path.isabs(src_path):
6161 raise errors.OpPrereqError("Importing an instance from an absolute"
6162 " path requires a source node option.",
6165 self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
6166 if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
6167 self.needed_locks[locking.LEVEL_NODE].append(src_node)
6168 if not os.path.isabs(src_path):
6169 self.op.src_path = src_path = \
6170 utils.PathJoin(constants.EXPORT_DIR, src_path)
6172 def _RunAllocator(self):
6173 """Run the allocator based on input opcode.
6176 nics = [n.ToDict() for n in self.nics]
6177 ial = IAllocator(self.cfg, self.rpc,
6178 mode=constants.IALLOCATOR_MODE_ALLOC,
6179 name=self.op.instance_name,
6180 disk_template=self.op.disk_template,
6183 vcpus=self.be_full[constants.BE_VCPUS],
6184 mem_size=self.be_full[constants.BE_MEMORY],
6187 hypervisor=self.op.hypervisor,
6190 ial.Run(self.op.iallocator)
6193 raise errors.OpPrereqError("Can't compute nodes using"
6194 " iallocator '%s': %s" %
6195 (self.op.iallocator, ial.info),
6197 if len(ial.result) != ial.required_nodes:
6198 raise errors.OpPrereqError("iallocator '%s' returned invalid number"
6199 " of nodes (%s), required %s" %
6200 (self.op.iallocator, len(ial.result),
6201 ial.required_nodes), errors.ECODE_FAULT)
6202 self.op.pnode = ial.result[0]
6203 self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
6204 self.op.instance_name, self.op.iallocator,
6205 utils.CommaJoin(ial.result))
6206 if ial.required_nodes == 2:
6207 self.op.snode = ial.result[1]
6209 def BuildHooksEnv(self):
6212 This runs on master, primary and secondary nodes of the instance.
6216 "ADD_MODE": self.op.mode,
6218 if self.op.mode == constants.INSTANCE_IMPORT:
6219 env["SRC_NODE"] = self.op.src_node
6220 env["SRC_PATH"] = self.op.src_path
6221 env["SRC_IMAGES"] = self.src_images
6223 env.update(_BuildInstanceHookEnv(
6224 name=self.op.instance_name,
6225 primary_node=self.op.pnode,
6226 secondary_nodes=self.secondaries,
6227 status=self.op.start,
6228 os_type=self.op.os_type,
6229 memory=self.be_full[constants.BE_MEMORY],
6230 vcpus=self.be_full[constants.BE_VCPUS],
6231 nics=_NICListToTuple(self, self.nics),
6232 disk_template=self.op.disk_template,
6233 disks=[(d["size"], d["mode"]) for d in self.disks],
6236 hypervisor_name=self.op.hypervisor,
6239 nl = ([self.cfg.GetMasterNode(), self.op.pnode] +
6243 def _ReadExportInfo(self):
6244 """Reads the export information from disk.
6246 It will override the opcode source node and path with the actual
6247 information, if these two were not specified before.
6249 @return: the export information
6252 assert self.op.mode == constants.INSTANCE_IMPORT
6254 src_node = self.op.src_node
6255 src_path = self.op.src_path
6257 if src_node is None:
6258 locked_nodes = self.acquired_locks[locking.LEVEL_NODE]
6259 exp_list = self.rpc.call_export_list(locked_nodes)
6261 for node in exp_list:
6262 if exp_list[node].fail_msg:
6264 if src_path in exp_list[node].payload:
6266 self.op.src_node = src_node = node
6267 self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
6271 raise errors.OpPrereqError("No export found for relative path %s" %
6272 src_path, errors.ECODE_INVAL)
6274 _CheckNodeOnline(self, src_node)
6275 result = self.rpc.call_export_info(src_node, src_path)
6276 result.Raise("No export or invalid export found in dir %s" % src_path)
6278 export_info = objects.SerializableConfigParser.Loads(str(result.payload))
6279 if not export_info.has_section(constants.INISECT_EXP):
6280 raise errors.ProgrammerError("Corrupted export config",
6281 errors.ECODE_ENVIRON)
6283 ei_version = export_info.get(constants.INISECT_EXP, "version")
6284 if (int(ei_version) != constants.EXPORT_VERSION):
6285 raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
6286 (ei_version, constants.EXPORT_VERSION),
6287 errors.ECODE_ENVIRON)
6290 def _ReadExportParams(self, einfo):
6291 """Use export parameters as defaults.
6293 In case the opcode doesn't specify (as in override) some instance
6294 parameters, then try to use them from the export information, if
6298 self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
6300 if self.op.disk_template is None:
6301 if einfo.has_option(constants.INISECT_INS, "disk_template"):
6302 self.op.disk_template = einfo.get(constants.INISECT_INS,
6305 raise errors.OpPrereqError("No disk template specified and the export"
6306 " is missing the disk_template information",
6309 if not self.op.disks:
6310 if einfo.has_option(constants.INISECT_INS, "disk_count"):
6312 # TODO: import the disk iv_name too
6313 for idx in range(einfo.getint(constants.INISECT_INS, "disk_count")):
6314 disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
6315 disks.append({"size": disk_sz})
6316 self.op.disks = disks
6318 raise errors.OpPrereqError("No disk info specified and the export"
6319 " is missing the disk information",
6322 if (not self.op.nics and
6323 einfo.has_option(constants.INISECT_INS, "nic_count")):
6325 for idx in range(einfo.getint(constants.INISECT_INS, "nic_count")):
6327 for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
6328 v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
6333 if (self.op.hypervisor is None and
6334 einfo.has_option(constants.INISECT_INS, "hypervisor")):
6335 self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
6336 if einfo.has_section(constants.INISECT_HYP):
6337 # use the export parameters but do not override the ones
6338 # specified by the user
6339 for name, value in einfo.items(constants.INISECT_HYP):
6340 if name not in self.op.hvparams:
6341 self.op.hvparams[name] = value
6343 if einfo.has_section(constants.INISECT_BEP):
6344 # use the parameters, without overriding
6345 for name, value in einfo.items(constants.INISECT_BEP):
6346 if name not in self.op.beparams:
6347 self.op.beparams[name] = value
6349 # try to read the parameters old style, from the main section
6350 for name in constants.BES_PARAMETERS:
6351 if (name not in self.op.beparams and
6352 einfo.has_option(constants.INISECT_INS, name)):
6353 self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
6355 def _RevertToDefaults(self, cluster):
6356 """Revert the instance parameters to the default values.
6360 hv_defs = cluster.GetHVDefaults(self.op.hypervisor, self.op.os_type)
6361 for name in self.op.hvparams.keys():
6362 if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
6363 del self.op.hvparams[name]
6365 be_defs = cluster.beparams.get(constants.PP_DEFAULT, {})
6366 for name in self.op.beparams.keys():
6367 if name in be_defs and be_defs[name] == self.op.beparams[name]:
6368 del self.op.beparams[name]
6370 nic_defs = cluster.nicparams.get(constants.PP_DEFAULT, {})
6371 for nic in self.op.nics:
6372 for name in constants.NICS_PARAMETERS:
6373 if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
6376 def CheckPrereq(self):
6377 """Check prerequisites.
6380 if self.op.mode == constants.INSTANCE_IMPORT:
6381 export_info = self._ReadExportInfo()
6382 self._ReadExportParams(export_info)
6384 _CheckDiskTemplate(self.op.disk_template)
6386 if (not self.cfg.GetVGName() and
6387 self.op.disk_template not in constants.DTS_NOT_LVM):
6388 raise errors.OpPrereqError("Cluster does not support lvm-based"
6389 " instances", errors.ECODE_STATE)
6391 if self.op.hypervisor is None:
6392 self.op.hypervisor = self.cfg.GetHypervisorType()
6394 cluster = self.cfg.GetClusterInfo()
6395 enabled_hvs = cluster.enabled_hypervisors
6396 if self.op.hypervisor not in enabled_hvs:
6397 raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
6398 " cluster (%s)" % (self.op.hypervisor,
6399 ",".join(enabled_hvs)),
6402 # check hypervisor parameter syntax (locally)
6403 utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6404 filled_hvp = objects.FillDict(cluster.GetHVDefaults(self.op.hypervisor,
6407 hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
6408 hv_type.CheckParameterSyntax(filled_hvp)
6409 self.hv_full = filled_hvp
6410 # check that we don't specify global parameters on an instance
6411 _CheckGlobalHvParams(self.op.hvparams)
6413 # fill and remember the beparams dict
6414 utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6415 self.be_full = objects.FillDict(cluster.beparams[constants.PP_DEFAULT],
6418 # now that hvp/bep are in final format, let's reset to defaults,
6420 if self.op.identify_defaults:
6421 self._RevertToDefaults(cluster)
6425 for idx, nic in enumerate(self.op.nics):
6426 nic_mode_req = nic.get("mode", None)
6427 nic_mode = nic_mode_req
6428 if nic_mode is None:
6429 nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
6431 # in routed mode, for the first nic, the default ip is 'auto'
6432 if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
6433 default_ip_mode = constants.VALUE_AUTO
6435 default_ip_mode = constants.VALUE_NONE
6437 # ip validity checks
6438 ip = nic.get("ip", default_ip_mode)
6439 if ip is None or ip.lower() == constants.VALUE_NONE:
6441 elif ip.lower() == constants.VALUE_AUTO:
6442 if not self.op.name_check:
6443 raise errors.OpPrereqError("IP address set to auto but name checks"
6444 " have been skipped. Aborting.",
6446 nic_ip = self.hostname1.ip
6448 if not utils.IsValidIP(ip):
6449 raise errors.OpPrereqError("Given IP address '%s' doesn't look"
6450 " like a valid IP" % ip,
6454 # TODO: check the ip address for uniqueness
6455 if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
6456 raise errors.OpPrereqError("Routed nic mode requires an ip address",
6459 # MAC address verification
6460 mac = nic.get("mac", constants.VALUE_AUTO)
6461 if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
6462 mac = utils.NormalizeAndValidateMac(mac)
6465 self.cfg.ReserveMAC(mac, self.proc.GetECId())
6466 except errors.ReservationError:
6467 raise errors.OpPrereqError("MAC address %s already in use"
6468 " in cluster" % mac,
6469 errors.ECODE_NOTUNIQUE)
6471 # bridge verification
6472 bridge = nic.get("bridge", None)
6473 link = nic.get("link", None)
6475 raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
6476 " at the same time", errors.ECODE_INVAL)
6477 elif bridge and nic_mode == constants.NIC_MODE_ROUTED:
6478 raise errors.OpPrereqError("Cannot pass 'bridge' on a routed nic",
6485 nicparams[constants.NIC_MODE] = nic_mode_req
6487 nicparams[constants.NIC_LINK] = link
6489 check_params = objects.FillDict(cluster.nicparams[constants.PP_DEFAULT],
6491 objects.NIC.CheckParameterSyntax(check_params)
6492 self.nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
6494 # disk checks/pre-build
6496 for disk in self.op.disks:
6497 mode = disk.get("mode", constants.DISK_RDWR)
6498 if mode not in constants.DISK_ACCESS_SET:
6499 raise errors.OpPrereqError("Invalid disk access mode '%s'" %
6500 mode, errors.ECODE_INVAL)
6501 size = disk.get("size", None)
6503 raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
6506 except (TypeError, ValueError):
6507 raise errors.OpPrereqError("Invalid disk size '%s'" % size,
6509 new_disk = {"size": size, "mode": mode}
6511 new_disk["adopt"] = disk["adopt"]
6512 self.disks.append(new_disk)
6514 if self.op.mode == constants.INSTANCE_IMPORT:
6516 # Check that the new instance doesn't have less disks than the export
6517 instance_disks = len(self.disks)
6518 export_disks = export_info.getint(constants.INISECT_INS, 'disk_count')
6519 if instance_disks < export_disks:
6520 raise errors.OpPrereqError("Not enough disks to import."
6521 " (instance: %d, export: %d)" %
6522 (instance_disks, export_disks),
6526 for idx in range(export_disks):
6527 option = 'disk%d_dump' % idx
6528 if export_info.has_option(constants.INISECT_INS, option):
6529 # FIXME: are the old os-es, disk sizes, etc. useful?
6530 export_name = export_info.get(constants.INISECT_INS, option)
6531 image = utils.PathJoin(self.op.src_path, export_name)
6532 disk_images.append(image)
6534 disk_images.append(False)
6536 self.src_images = disk_images
6538 old_name = export_info.get(constants.INISECT_INS, 'name')
6540 exp_nic_count = export_info.getint(constants.INISECT_INS, 'nic_count')
6541 except (TypeError, ValueError), err:
6542 raise errors.OpPrereqError("Invalid export file, nic_count is not"
6543 " an integer: %s" % str(err),
6545 if self.op.instance_name == old_name:
6546 for idx, nic in enumerate(self.nics):
6547 if nic.mac == constants.VALUE_AUTO and exp_nic_count >= idx:
6548 nic_mac_ini = 'nic%d_mac' % idx
6549 nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
6551 # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
6553 # ip ping checks (we use the same ip that was resolved in ExpandNames)
6554 if self.op.ip_check:
6555 if utils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
6556 raise errors.OpPrereqError("IP %s of instance %s already in use" %
6557 (self.check_ip, self.op.instance_name),
6558 errors.ECODE_NOTUNIQUE)
6560 #### mac address generation
6561 # By generating here the mac address both the allocator and the hooks get
6562 # the real final mac address rather than the 'auto' or 'generate' value.
6563 # There is a race condition between the generation and the instance object
6564 # creation, which means that we know the mac is valid now, but we're not
6565 # sure it will be when we actually add the instance. If things go bad
6566 # adding the instance will abort because of a duplicate mac, and the
6567 # creation job will fail.
6568 for nic in self.nics:
6569 if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
6570 nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
6574 if self.op.iallocator is not None:
6575 self._RunAllocator()
6577 #### node related checks
6579 # check primary node
6580 self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
6581 assert self.pnode is not None, \
6582 "Cannot retrieve locked node %s" % self.op.pnode
6584 raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
6585 pnode.name, errors.ECODE_STATE)
6587 raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
6588 pnode.name, errors.ECODE_STATE)
6590 self.secondaries = []
6592 # mirror node verification
6593 if self.op.disk_template in constants.DTS_NET_MIRROR:
6594 if self.op.snode is None:
6595 raise errors.OpPrereqError("The networked disk templates need"
6596 " a mirror node", errors.ECODE_INVAL)
6597 if self.op.snode == pnode.name:
6598 raise errors.OpPrereqError("The secondary node cannot be the"
6599 " primary node.", errors.ECODE_INVAL)
6600 _CheckNodeOnline(self, self.op.snode)
6601 _CheckNodeNotDrained(self, self.op.snode)
6602 self.secondaries.append(self.op.snode)
6604 nodenames = [pnode.name] + self.secondaries
6606 req_size = _ComputeDiskSize(self.op.disk_template,
6609 # Check lv size requirements, if not adopting
6610 if req_size is not None and not self.adopt_disks:
6611 _CheckNodesFreeDisk(self, nodenames, req_size)
6613 if self.adopt_disks: # instead, we must check the adoption data
6614 all_lvs = set([i["adopt"] for i in self.disks])
6615 if len(all_lvs) != len(self.disks):
6616 raise errors.OpPrereqError("Duplicate volume names given for adoption",
6618 for lv_name in all_lvs:
6620 self.cfg.ReserveLV(lv_name, self.proc.GetECId())
6621 except errors.ReservationError:
6622 raise errors.OpPrereqError("LV named %s used by another instance" %
6623 lv_name, errors.ECODE_NOTUNIQUE)
6625 node_lvs = self.rpc.call_lv_list([pnode.name],
6626 self.cfg.GetVGName())[pnode.name]
6627 node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
6628 node_lvs = node_lvs.payload
6629 delta = all_lvs.difference(node_lvs.keys())
6631 raise errors.OpPrereqError("Missing logical volume(s): %s" %
6632 utils.CommaJoin(delta),
6634 online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
6636 raise errors.OpPrereqError("Online logical volumes found, cannot"
6637 " adopt: %s" % utils.CommaJoin(online_lvs),
6639 # update the size of disk based on what is found
6640 for dsk in self.disks:
6641 dsk["size"] = int(float(node_lvs[dsk["adopt"]][0]))
6643 _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
6645 _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
6647 _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
6649 # memory check on primary node
6651 _CheckNodeFreeMemory(self, self.pnode.name,
6652 "creating instance %s" % self.op.instance_name,
6653 self.be_full[constants.BE_MEMORY],
6656 self.dry_run_result = list(nodenames)
6658 def Exec(self, feedback_fn):
6659 """Create and add the instance to the cluster.
6662 instance = self.op.instance_name
6663 pnode_name = self.pnode.name
6665 ht_kind = self.op.hypervisor
6666 if ht_kind in constants.HTS_REQ_PORT:
6667 network_port = self.cfg.AllocatePort()
6671 if constants.ENABLE_FILE_STORAGE:
6672 # this is needed because os.path.join does not accept None arguments
6673 if self.op.file_storage_dir is None:
6674 string_file_storage_dir = ""
6676 string_file_storage_dir = self.op.file_storage_dir
6678 # build the full file storage dir path
6679 file_storage_dir = utils.PathJoin(self.cfg.GetFileStorageDir(),
6680 string_file_storage_dir, instance)
6682 file_storage_dir = ""
6685 disks = _GenerateDiskTemplate(self,
6686 self.op.disk_template,
6687 instance, pnode_name,
6691 self.op.file_driver,
6694 iobj = objects.Instance(name=instance, os=self.op.os_type,
6695 primary_node=pnode_name,
6696 nics=self.nics, disks=disks,
6697 disk_template=self.op.disk_template,
6699 network_port=network_port,
6700 beparams=self.op.beparams,
6701 hvparams=self.op.hvparams,
6702 hypervisor=self.op.hypervisor,
6705 if self.adopt_disks:
6706 # rename LVs to the newly-generated names; we need to construct
6707 # 'fake' LV disks with the old data, plus the new unique_id
6708 tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
6710 for t_dsk, a_dsk in zip (tmp_disks, self.disks):
6711 rename_to.append(t_dsk.logical_id)
6712 t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk["adopt"])
6713 self.cfg.SetDiskID(t_dsk, pnode_name)
6714 result = self.rpc.call_blockdev_rename(pnode_name,
6715 zip(tmp_disks, rename_to))
6716 result.Raise("Failed to rename adoped LVs")
6718 feedback_fn("* creating instance disks...")
6720 _CreateDisks(self, iobj)
6721 except errors.OpExecError:
6722 self.LogWarning("Device creation failed, reverting...")
6724 _RemoveDisks(self, iobj)
6726 self.cfg.ReleaseDRBDMinors(instance)
6729 feedback_fn("adding instance %s to cluster config" % instance)
6731 self.cfg.AddInstance(iobj, self.proc.GetECId())
6733 # Declare that we don't want to remove the instance lock anymore, as we've
6734 # added the instance to the config
6735 del self.remove_locks[locking.LEVEL_INSTANCE]
6736 # Unlock all the nodes
6737 if self.op.mode == constants.INSTANCE_IMPORT:
6738 nodes_keep = [self.op.src_node]
6739 nodes_release = [node for node in self.acquired_locks[locking.LEVEL_NODE]
6740 if node != self.op.src_node]
6741 self.context.glm.release(locking.LEVEL_NODE, nodes_release)
6742 self.acquired_locks[locking.LEVEL_NODE] = nodes_keep
6744 self.context.glm.release(locking.LEVEL_NODE)
6745 del self.acquired_locks[locking.LEVEL_NODE]
6747 if self.op.wait_for_sync:
6748 disk_abort = not _WaitForSync(self, iobj)
6749 elif iobj.disk_template in constants.DTS_NET_MIRROR:
6750 # make sure the disks are not degraded (still sync-ing is ok)
6752 feedback_fn("* checking mirrors status")
6753 disk_abort = not _WaitForSync(self, iobj, oneshot=True)
6758 _RemoveDisks(self, iobj)
6759 self.cfg.RemoveInstance(iobj.name)
6760 # Make sure the instance lock gets removed
6761 self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
6762 raise errors.OpExecError("There are some degraded disks for"
6765 if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
6766 if self.op.mode == constants.INSTANCE_CREATE:
6767 if not self.op.no_install:
6768 feedback_fn("* running the instance OS create scripts...")
6769 # FIXME: pass debug option from opcode to backend
6770 result = self.rpc.call_instance_os_add(pnode_name, iobj, False,
6771 self.op.debug_level)
6772 result.Raise("Could not add os for instance %s"
6773 " on node %s" % (instance, pnode_name))
6775 elif self.op.mode == constants.INSTANCE_IMPORT:
6776 feedback_fn("* running the instance OS import scripts...")
6777 src_node = self.op.src_node
6778 src_images = self.src_images
6779 cluster_name = self.cfg.GetClusterName()
6780 # FIXME: pass debug option from opcode to backend
6781 import_result = self.rpc.call_instance_os_import(pnode_name, iobj,
6782 src_node, src_images,
6784 self.op.debug_level)
6785 msg = import_result.fail_msg
6787 self.LogWarning("Error while importing the disk images for instance"
6788 " %s on node %s: %s" % (instance, pnode_name, msg))
6790 # also checked in the prereq part
6791 raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
6795 iobj.admin_up = True
6796 self.cfg.Update(iobj, feedback_fn)
6797 logging.info("Starting instance %s on node %s", instance, pnode_name)
6798 feedback_fn("* starting instance...")
6799 result = self.rpc.call_instance_start(pnode_name, iobj, None, None)
6800 result.Raise("Could not start instance")
6802 return list(iobj.all_nodes)
6805 class LUConnectConsole(NoHooksLU):
6806 """Connect to an instance's console.
6808 This is somewhat special in that it returns the command line that
6809 you need to run on the master node in order to connect to the
6813 _OP_REQP = ["instance_name"]
6816 def ExpandNames(self):
6817 self._ExpandAndLockInstance()
6819 def CheckPrereq(self):
6820 """Check prerequisites.
6822 This checks that the instance is in the cluster.
6825 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6826 assert self.instance is not None, \
6827 "Cannot retrieve locked instance %s" % self.op.instance_name
6828 _CheckNodeOnline(self, self.instance.primary_node)
6830 def Exec(self, feedback_fn):
6831 """Connect to the console of an instance
6834 instance = self.instance
6835 node = instance.primary_node
6837 node_insts = self.rpc.call_instance_list([node],
6838 [instance.hypervisor])[node]
6839 node_insts.Raise("Can't get node information from %s" % node)
6841 if instance.name not in node_insts.payload:
6842 raise errors.OpExecError("Instance %s is not running." % instance.name)
6844 logging.debug("Connecting to console of %s on %s", instance.name, node)
6846 hyper = hypervisor.GetHypervisor(instance.hypervisor)
6847 cluster = self.cfg.GetClusterInfo()
6848 # beparams and hvparams are passed separately, to avoid editing the
6849 # instance and then saving the defaults in the instance itself.
6850 hvparams = cluster.FillHV(instance)
6851 beparams = cluster.FillBE(instance)
6852 console_cmd = hyper.GetShellCommandForConsole(instance, hvparams, beparams)
6855 return self.ssh.BuildCmd(node, "root", console_cmd, batch=True, tty=True)
6858 class LUReplaceDisks(LogicalUnit):
6859 """Replace the disks of an instance.
6862 HPATH = "mirrors-replace"
6863 HTYPE = constants.HTYPE_INSTANCE
6864 _OP_REQP = ["instance_name", "mode", "disks"]
6867 def CheckArguments(self):
6868 if not hasattr(self.op, "remote_node"):
6869 self.op.remote_node = None
6870 if not hasattr(self.op, "iallocator"):
6871 self.op.iallocator = None
6872 if not hasattr(self.op, "early_release"):
6873 self.op.early_release = False
6875 TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
6878 def ExpandNames(self):
6879 self._ExpandAndLockInstance()
6881 if self.op.iallocator is not None:
6882 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6884 elif self.op.remote_node is not None:
6885 remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
6886 self.op.remote_node = remote_node
6888 # Warning: do not remove the locking of the new secondary here
6889 # unless DRBD8.AddChildren is changed to work in parallel;
6890 # currently it doesn't since parallel invocations of
6891 # FindUnusedMinor will conflict
6892 self.needed_locks[locking.LEVEL_NODE] = [remote_node]
6893 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
6896 self.needed_locks[locking.LEVEL_NODE] = []
6897 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6899 self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
6900 self.op.iallocator, self.op.remote_node,
6901 self.op.disks, False, self.op.early_release)
6903 self.tasklets = [self.replacer]
6905 def DeclareLocks(self, level):
6906 # If we're not already locking all nodes in the set we have to declare the
6907 # instance's primary/secondary nodes.
6908 if (level == locking.LEVEL_NODE and
6909 self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
6910 self._LockInstancesNodes()
6912 def BuildHooksEnv(self):
6915 This runs on the master, the primary and all the secondaries.
6918 instance = self.replacer.instance
6920 "MODE": self.op.mode,
6921 "NEW_SECONDARY": self.op.remote_node,
6922 "OLD_SECONDARY": instance.secondary_nodes[0],
6924 env.update(_BuildInstanceHookEnvByObject(self, instance))
6926 self.cfg.GetMasterNode(),
6927 instance.primary_node,
6929 if self.op.remote_node is not None:
6930 nl.append(self.op.remote_node)
6934 class LUEvacuateNode(LogicalUnit):
6935 """Relocate the secondary instances from a node.
6938 HPATH = "node-evacuate"
6939 HTYPE = constants.HTYPE_NODE
6940 _OP_REQP = ["node_name"]
6943 def CheckArguments(self):
6944 if not hasattr(self.op, "remote_node"):
6945 self.op.remote_node = None
6946 if not hasattr(self.op, "iallocator"):
6947 self.op.iallocator = None
6948 if not hasattr(self.op, "early_release"):
6949 self.op.early_release = False
6951 TLReplaceDisks.CheckArguments(constants.REPLACE_DISK_CHG,
6952 self.op.remote_node,
6955 def ExpandNames(self):
6956 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6958 self.needed_locks = {}
6960 # Declare node locks
6961 if self.op.iallocator is not None:
6962 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6964 elif self.op.remote_node is not None:
6965 self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
6967 # Warning: do not remove the locking of the new secondary here
6968 # unless DRBD8.AddChildren is changed to work in parallel;
6969 # currently it doesn't since parallel invocations of
6970 # FindUnusedMinor will conflict
6971 self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
6972 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
6975 raise errors.OpPrereqError("Invalid parameters", errors.ECODE_INVAL)
6977 # Create tasklets for replacing disks for all secondary instances on this
6982 for inst in _GetNodeSecondaryInstances(self.cfg, self.op.node_name):
6983 logging.debug("Replacing disks for instance %s", inst.name)
6984 names.append(inst.name)
6986 replacer = TLReplaceDisks(self, inst.name, constants.REPLACE_DISK_CHG,
6987 self.op.iallocator, self.op.remote_node, [],
6988 True, self.op.early_release)
6989 tasklets.append(replacer)
6991 self.tasklets = tasklets
6992 self.instance_names = names
6994 # Declare instance locks
6995 self.needed_locks[locking.LEVEL_INSTANCE] = self.instance_names
6997 def DeclareLocks(self, level):
6998 # If we're not already locking all nodes in the set we have to declare the
6999 # instance's primary/secondary nodes.
7000 if (level == locking.LEVEL_NODE and
7001 self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
7002 self._LockInstancesNodes()
7004 def BuildHooksEnv(self):
7007 This runs on the master, the primary and all the secondaries.
7011 "NODE_NAME": self.op.node_name,
7014 nl = [self.cfg.GetMasterNode()]
7016 if self.op.remote_node is not None:
7017 env["NEW_SECONDARY"] = self.op.remote_node
7018 nl.append(self.op.remote_node)
7020 return (env, nl, nl)
7023 class TLReplaceDisks(Tasklet):
7024 """Replaces disks for an instance.
7026 Note: Locking is not within the scope of this class.
7029 def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
7030 disks, delay_iallocator, early_release):
7031 """Initializes this class.
7034 Tasklet.__init__(self, lu)
7037 self.instance_name = instance_name
7039 self.iallocator_name = iallocator_name
7040 self.remote_node = remote_node
7042 self.delay_iallocator = delay_iallocator
7043 self.early_release = early_release
7046 self.instance = None
7047 self.new_node = None
7048 self.target_node = None
7049 self.other_node = None
7050 self.remote_node_info = None
7051 self.node_secondary_ip = None
7054 def CheckArguments(mode, remote_node, iallocator):
7055 """Helper function for users of this class.
7058 # check for valid parameter combination
7059 if mode == constants.REPLACE_DISK_CHG:
7060 if remote_node is None and iallocator is None:
7061 raise errors.OpPrereqError("When changing the secondary either an"
7062 " iallocator script must be used or the"
7063 " new node given", errors.ECODE_INVAL)
7065 if remote_node is not None and iallocator is not None:
7066 raise errors.OpPrereqError("Give either the iallocator or the new"
7067 " secondary, not both", errors.ECODE_INVAL)
7069 elif remote_node is not None or iallocator is not None:
7070 # Not replacing the secondary
7071 raise errors.OpPrereqError("The iallocator and new node options can"
7072 " only be used when changing the"
7073 " secondary node", errors.ECODE_INVAL)
7076 def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
7077 """Compute a new secondary node using an IAllocator.
7080 ial = IAllocator(lu.cfg, lu.rpc,
7081 mode=constants.IALLOCATOR_MODE_RELOC,
7083 relocate_from=relocate_from)
7085 ial.Run(iallocator_name)
7088 raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
7089 " %s" % (iallocator_name, ial.info),
7092 if len(ial.result) != ial.required_nodes:
7093 raise errors.OpPrereqError("iallocator '%s' returned invalid number"
7094 " of nodes (%s), required %s" %
7096 len(ial.result), ial.required_nodes),
7099 remote_node_name = ial.result[0]
7101 lu.LogInfo("Selected new secondary for instance '%s': %s",
7102 instance_name, remote_node_name)
7104 return remote_node_name
7106 def _FindFaultyDisks(self, node_name):
7107 return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
7110 def CheckPrereq(self):
7111 """Check prerequisites.
7113 This checks that the instance is in the cluster.
7116 self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
7117 assert instance is not None, \
7118 "Cannot retrieve locked instance %s" % self.instance_name
7120 if instance.disk_template != constants.DT_DRBD8:
7121 raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
7122 " instances", errors.ECODE_INVAL)
7124 if len(instance.secondary_nodes) != 1:
7125 raise errors.OpPrereqError("The instance has a strange layout,"
7126 " expected one secondary but found %d" %
7127 len(instance.secondary_nodes),
7130 if not self.delay_iallocator:
7131 self._CheckPrereq2()
7133 def _CheckPrereq2(self):
7134 """Check prerequisites, second part.
7136 This function should always be part of CheckPrereq. It was separated and is
7137 now called from Exec because during node evacuation iallocator was only
7138 called with an unmodified cluster model, not taking planned changes into
7142 instance = self.instance
7143 secondary_node = instance.secondary_nodes[0]
7145 if self.iallocator_name is None:
7146 remote_node = self.remote_node
7148 remote_node = self._RunAllocator(self.lu, self.iallocator_name,
7149 instance.name, instance.secondary_nodes)
7151 if remote_node is not None:
7152 self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
7153 assert self.remote_node_info is not None, \
7154 "Cannot retrieve locked node %s" % remote_node
7156 self.remote_node_info = None
7158 if remote_node == self.instance.primary_node:
7159 raise errors.OpPrereqError("The specified node is the primary node of"
7160 " the instance.", errors.ECODE_INVAL)
7162 if remote_node == secondary_node:
7163 raise errors.OpPrereqError("The specified node is already the"
7164 " secondary node of the instance.",
7167 if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
7168 constants.REPLACE_DISK_CHG):
7169 raise errors.OpPrereqError("Cannot specify disks to be replaced",
7172 if self.mode == constants.REPLACE_DISK_AUTO:
7173 faulty_primary = self._FindFaultyDisks(instance.primary_node)
7174 faulty_secondary = self._FindFaultyDisks(secondary_node)
7176 if faulty_primary and faulty_secondary:
7177 raise errors.OpPrereqError("Instance %s has faulty disks on more than"
7178 " one node and can not be repaired"
7179 " automatically" % self.instance_name,
7183 self.disks = faulty_primary
7184 self.target_node = instance.primary_node
7185 self.other_node = secondary_node
7186 check_nodes = [self.target_node, self.other_node]
7187 elif faulty_secondary:
7188 self.disks = faulty_secondary
7189 self.target_node = secondary_node
7190 self.other_node = instance.primary_node
7191 check_nodes = [self.target_node, self.other_node]
7197 # Non-automatic modes
7198 if self.mode == constants.REPLACE_DISK_PRI:
7199 self.target_node = instance.primary_node
7200 self.other_node = secondary_node
7201 check_nodes = [self.target_node, self.other_node]
7203 elif self.mode == constants.REPLACE_DISK_SEC:
7204 self.target_node = secondary_node
7205 self.other_node = instance.primary_node
7206 check_nodes = [self.target_node, self.other_node]
7208 elif self.mode == constants.REPLACE_DISK_CHG:
7209 self.new_node = remote_node
7210 self.other_node = instance.primary_node
7211 self.target_node = secondary_node
7212 check_nodes = [self.new_node, self.other_node]
7214 _CheckNodeNotDrained(self.lu, remote_node)
7216 old_node_info = self.cfg.GetNodeInfo(secondary_node)
7217 assert old_node_info is not None
7218 if old_node_info.offline and not self.early_release:
7219 # doesn't make sense to delay the release
7220 self.early_release = True
7221 self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
7222 " early-release mode", secondary_node)
7225 raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
7228 # If not specified all disks should be replaced
7230 self.disks = range(len(self.instance.disks))
7232 for node in check_nodes:
7233 _CheckNodeOnline(self.lu, node)
7235 # Check whether disks are valid
7236 for disk_idx in self.disks:
7237 instance.FindDisk(disk_idx)
7239 # Get secondary node IP addresses
7242 for node_name in [self.target_node, self.other_node, self.new_node]:
7243 if node_name is not None:
7244 node_2nd_ip[node_name] = self.cfg.GetNodeInfo(node_name).secondary_ip
7246 self.node_secondary_ip = node_2nd_ip
7248 def Exec(self, feedback_fn):
7249 """Execute disk replacement.
7251 This dispatches the disk replacement to the appropriate handler.
7254 if self.delay_iallocator:
7255 self._CheckPrereq2()
7258 feedback_fn("No disks need replacement")
7261 feedback_fn("Replacing disk(s) %s for %s" %
7262 (utils.CommaJoin(self.disks), self.instance.name))
7264 activate_disks = (not self.instance.admin_up)
7266 # Activate the instance disks if we're replacing them on a down instance
7268 _StartInstanceDisks(self.lu, self.instance, True)
7271 # Should we replace the secondary node?
7272 if self.new_node is not None:
7273 fn = self._ExecDrbd8Secondary
7275 fn = self._ExecDrbd8DiskOnly
7277 return fn(feedback_fn)
7280 # Deactivate the instance disks if we're replacing them on a
7283 _SafeShutdownInstanceDisks(self.lu, self.instance)
7285 def _CheckVolumeGroup(self, nodes):
7286 self.lu.LogInfo("Checking volume groups")
7288 vgname = self.cfg.GetVGName()
7290 # Make sure volume group exists on all involved nodes
7291 results = self.rpc.call_vg_list(nodes)
7293 raise errors.OpExecError("Can't list volume groups on the nodes")
7297 res.Raise("Error checking node %s" % node)
7298 if vgname not in res.payload:
7299 raise errors.OpExecError("Volume group '%s' not found on node %s" %
7302 def _CheckDisksExistence(self, nodes):
7303 # Check disk existence
7304 for idx, dev in enumerate(self.instance.disks):
7305 if idx not in self.disks:
7309 self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
7310 self.cfg.SetDiskID(dev, node)
7312 result = self.rpc.call_blockdev_find(node, dev)
7314 msg = result.fail_msg
7315 if msg or not result.payload:
7317 msg = "disk not found"
7318 raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
7321 def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
7322 for idx, dev in enumerate(self.instance.disks):
7323 if idx not in self.disks:
7326 self.lu.LogInfo("Checking disk/%d consistency on node %s" %
7329 if not _CheckDiskConsistency(self.lu, dev, node_name, on_primary,
7331 raise errors.OpExecError("Node %s has degraded storage, unsafe to"
7332 " replace disks for instance %s" %
7333 (node_name, self.instance.name))
7335 def _CreateNewStorage(self, node_name):
7336 vgname = self.cfg.GetVGName()
7339 for idx, dev in enumerate(self.instance.disks):
7340 if idx not in self.disks:
7343 self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
7345 self.cfg.SetDiskID(dev, node_name)
7347 lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
7348 names = _GenerateUniqueNames(self.lu, lv_names)
7350 lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
7351 logical_id=(vgname, names[0]))
7352 lv_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
7353 logical_id=(vgname, names[1]))
7355 new_lvs = [lv_data, lv_meta]
7356 old_lvs = dev.children
7357 iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
7359 # we pass force_create=True to force the LVM creation
7360 for new_lv in new_lvs:
7361 _CreateBlockDev(self.lu, node_name, self.instance, new_lv, True,
7362 _GetInstanceInfoText(self.instance), False)
7366 def _CheckDevices(self, node_name, iv_names):
7367 for name, (dev, _, _) in iv_names.iteritems():
7368 self.cfg.SetDiskID(dev, node_name)
7370 result = self.rpc.call_blockdev_find(node_name, dev)
7372 msg = result.fail_msg
7373 if msg or not result.payload:
7375 msg = "disk not found"
7376 raise errors.OpExecError("Can't find DRBD device %s: %s" %
7379 if result.payload.is_degraded:
7380 raise errors.OpExecError("DRBD device %s is degraded!" % name)
7382 def _RemoveOldStorage(self, node_name, iv_names):
7383 for name, (_, old_lvs, _) in iv_names.iteritems():
7384 self.lu.LogInfo("Remove logical volumes for %s" % name)
7387 self.cfg.SetDiskID(lv, node_name)
7389 msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
7391 self.lu.LogWarning("Can't remove old LV: %s" % msg,
7392 hint="remove unused LVs manually")
7394 def _ReleaseNodeLock(self, node_name):
7395 """Releases the lock for a given node."""
7396 self.lu.context.glm.release(locking.LEVEL_NODE, node_name)
7398 def _ExecDrbd8DiskOnly(self, feedback_fn):
7399 """Replace a disk on the primary or secondary for DRBD 8.
7401 The algorithm for replace is quite complicated:
7403 1. for each disk to be replaced:
7405 1. create new LVs on the target node with unique names
7406 1. detach old LVs from the drbd device
7407 1. rename old LVs to name_replaced.<time_t>
7408 1. rename new LVs to old LVs
7409 1. attach the new LVs (with the old names now) to the drbd device
7411 1. wait for sync across all devices
7413 1. for each modified disk:
7415 1. remove old LVs (which have the name name_replaces.<time_t>)
7417 Failures are not very well handled.
7422 # Step: check device activation
7423 self.lu.LogStep(1, steps_total, "Check device existence")
7424 self._CheckDisksExistence([self.other_node, self.target_node])
7425 self._CheckVolumeGroup([self.target_node, self.other_node])
7427 # Step: check other node consistency
7428 self.lu.LogStep(2, steps_total, "Check peer consistency")
7429 self._CheckDisksConsistency(self.other_node,
7430 self.other_node == self.instance.primary_node,
7433 # Step: create new storage
7434 self.lu.LogStep(3, steps_total, "Allocate new storage")
7435 iv_names = self._CreateNewStorage(self.target_node)
7437 # Step: for each lv, detach+rename*2+attach
7438 self.lu.LogStep(4, steps_total, "Changing drbd configuration")
7439 for dev, old_lvs, new_lvs in iv_names.itervalues():
7440 self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
7442 result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
7444 result.Raise("Can't detach drbd from local storage on node"
7445 " %s for device %s" % (self.target_node, dev.iv_name))
7447 #cfg.Update(instance)
7449 # ok, we created the new LVs, so now we know we have the needed
7450 # storage; as such, we proceed on the target node to rename
7451 # old_lv to _old, and new_lv to old_lv; note that we rename LVs
7452 # using the assumption that logical_id == physical_id (which in
7453 # turn is the unique_id on that node)
7455 # FIXME(iustin): use a better name for the replaced LVs
7456 temp_suffix = int(time.time())
7457 ren_fn = lambda d, suff: (d.physical_id[0],
7458 d.physical_id[1] + "_replaced-%s" % suff)
7460 # Build the rename list based on what LVs exist on the node
7461 rename_old_to_new = []
7462 for to_ren in old_lvs:
7463 result = self.rpc.call_blockdev_find(self.target_node, to_ren)
7464 if not result.fail_msg and result.payload:
7466 rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
7468 self.lu.LogInfo("Renaming the old LVs on the target node")
7469 result = self.rpc.call_blockdev_rename(self.target_node,
7471 result.Raise("Can't rename old LVs on node %s" % self.target_node)
7473 # Now we rename the new LVs to the old LVs
7474 self.lu.LogInfo("Renaming the new LVs on the target node")
7475 rename_new_to_old = [(new, old.physical_id)
7476 for old, new in zip(old_lvs, new_lvs)]
7477 result = self.rpc.call_blockdev_rename(self.target_node,
7479 result.Raise("Can't rename new LVs on node %s" % self.target_node)
7481 for old, new in zip(old_lvs, new_lvs):
7482 new.logical_id = old.logical_id
7483 self.cfg.SetDiskID(new, self.target_node)
7485 for disk in old_lvs:
7486 disk.logical_id = ren_fn(disk, temp_suffix)
7487 self.cfg.SetDiskID(disk, self.target_node)
7489 # Now that the new lvs have the old name, we can add them to the device
7490 self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
7491 result = self.rpc.call_blockdev_addchildren(self.target_node, dev,
7493 msg = result.fail_msg
7495 for new_lv in new_lvs:
7496 msg2 = self.rpc.call_blockdev_remove(self.target_node,
7499 self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
7500 hint=("cleanup manually the unused logical"
7502 raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
7504 dev.children = new_lvs
7506 self.cfg.Update(self.instance, feedback_fn)
7509 if self.early_release:
7510 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7512 self._RemoveOldStorage(self.target_node, iv_names)
7513 # WARNING: we release both node locks here, do not do other RPCs
7514 # than WaitForSync to the primary node
7515 self._ReleaseNodeLock([self.target_node, self.other_node])
7518 # This can fail as the old devices are degraded and _WaitForSync
7519 # does a combined result over all disks, so we don't check its return value
7520 self.lu.LogStep(cstep, steps_total, "Sync devices")
7522 _WaitForSync(self.lu, self.instance)
7524 # Check all devices manually
7525 self._CheckDevices(self.instance.primary_node, iv_names)
7527 # Step: remove old storage
7528 if not self.early_release:
7529 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7531 self._RemoveOldStorage(self.target_node, iv_names)
7533 def _ExecDrbd8Secondary(self, feedback_fn):
7534 """Replace the secondary node for DRBD 8.
7536 The algorithm for replace is quite complicated:
7537 - for all disks of the instance:
7538 - create new LVs on the new node with same names
7539 - shutdown the drbd device on the old secondary
7540 - disconnect the drbd network on the primary
7541 - create the drbd device on the new secondary
7542 - network attach the drbd on the primary, using an artifice:
7543 the drbd code for Attach() will connect to the network if it
7544 finds a device which is connected to the good local disks but
7546 - wait for sync across all devices
7547 - remove all disks from the old secondary
7549 Failures are not very well handled.
7554 # Step: check device activation
7555 self.lu.LogStep(1, steps_total, "Check device existence")
7556 self._CheckDisksExistence([self.instance.primary_node])
7557 self._CheckVolumeGroup([self.instance.primary_node])
7559 # Step: check other node consistency
7560 self.lu.LogStep(2, steps_total, "Check peer consistency")
7561 self._CheckDisksConsistency(self.instance.primary_node, True, True)
7563 # Step: create new storage
7564 self.lu.LogStep(3, steps_total, "Allocate new storage")
7565 for idx, dev in enumerate(self.instance.disks):
7566 self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
7567 (self.new_node, idx))
7568 # we pass force_create=True to force LVM creation
7569 for new_lv in dev.children:
7570 _CreateBlockDev(self.lu, self.new_node, self.instance, new_lv, True,
7571 _GetInstanceInfoText(self.instance), False)
7573 # Step 4: dbrd minors and drbd setups changes
7574 # after this, we must manually remove the drbd minors on both the
7575 # error and the success paths
7576 self.lu.LogStep(4, steps_total, "Changing drbd configuration")
7577 minors = self.cfg.AllocateDRBDMinor([self.new_node
7578 for dev in self.instance.disks],
7580 logging.debug("Allocated minors %r", minors)
7583 for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
7584 self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
7585 (self.new_node, idx))
7586 # create new devices on new_node; note that we create two IDs:
7587 # one without port, so the drbd will be activated without
7588 # networking information on the new node at this stage, and one
7589 # with network, for the latter activation in step 4
7590 (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
7591 if self.instance.primary_node == o_node1:
7594 assert self.instance.primary_node == o_node2, "Three-node instance?"
7597 new_alone_id = (self.instance.primary_node, self.new_node, None,
7598 p_minor, new_minor, o_secret)
7599 new_net_id = (self.instance.primary_node, self.new_node, o_port,
7600 p_minor, new_minor, o_secret)
7602 iv_names[idx] = (dev, dev.children, new_net_id)
7603 logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
7605 new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
7606 logical_id=new_alone_id,
7607 children=dev.children,
7610 _CreateSingleBlockDev(self.lu, self.new_node, self.instance, new_drbd,
7611 _GetInstanceInfoText(self.instance), False)
7612 except errors.GenericError:
7613 self.cfg.ReleaseDRBDMinors(self.instance.name)
7616 # We have new devices, shutdown the drbd on the old secondary
7617 for idx, dev in enumerate(self.instance.disks):
7618 self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
7619 self.cfg.SetDiskID(dev, self.target_node)
7620 msg = self.rpc.call_blockdev_shutdown(self.target_node, dev).fail_msg
7622 self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
7623 "node: %s" % (idx, msg),
7624 hint=("Please cleanup this device manually as"
7625 " soon as possible"))
7627 self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
7628 result = self.rpc.call_drbd_disconnect_net([self.instance.primary_node],
7629 self.node_secondary_ip,
7630 self.instance.disks)\
7631 [self.instance.primary_node]
7633 msg = result.fail_msg
7635 # detaches didn't succeed (unlikely)
7636 self.cfg.ReleaseDRBDMinors(self.instance.name)
7637 raise errors.OpExecError("Can't detach the disks from the network on"
7638 " old node: %s" % (msg,))
7640 # if we managed to detach at least one, we update all the disks of
7641 # the instance to point to the new secondary
7642 self.lu.LogInfo("Updating instance configuration")
7643 for dev, _, new_logical_id in iv_names.itervalues():
7644 dev.logical_id = new_logical_id
7645 self.cfg.SetDiskID(dev, self.instance.primary_node)
7647 self.cfg.Update(self.instance, feedback_fn)
7649 # and now perform the drbd attach
7650 self.lu.LogInfo("Attaching primary drbds to new secondary"
7651 " (standalone => connected)")
7652 result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
7654 self.node_secondary_ip,
7655 self.instance.disks,
7658 for to_node, to_result in result.items():
7659 msg = to_result.fail_msg
7661 self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
7663 hint=("please do a gnt-instance info to see the"
7664 " status of disks"))
7666 if self.early_release:
7667 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7669 self._RemoveOldStorage(self.target_node, iv_names)
7670 # WARNING: we release all node locks here, do not do other RPCs
7671 # than WaitForSync to the primary node
7672 self._ReleaseNodeLock([self.instance.primary_node,
7677 # This can fail as the old devices are degraded and _WaitForSync
7678 # does a combined result over all disks, so we don't check its return value
7679 self.lu.LogStep(cstep, steps_total, "Sync devices")
7681 _WaitForSync(self.lu, self.instance)
7683 # Check all devices manually
7684 self._CheckDevices(self.instance.primary_node, iv_names)
7686 # Step: remove old storage
7687 if not self.early_release:
7688 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7689 self._RemoveOldStorage(self.target_node, iv_names)
7692 class LURepairNodeStorage(NoHooksLU):
7693 """Repairs the volume group on a node.
7696 _OP_REQP = ["node_name"]
7699 def CheckArguments(self):
7700 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
7702 _CheckStorageType(self.op.storage_type)
7704 def ExpandNames(self):
7705 self.needed_locks = {
7706 locking.LEVEL_NODE: [self.op.node_name],
7709 def _CheckFaultyDisks(self, instance, node_name):
7710 """Ensure faulty disks abort the opcode or at least warn."""
7712 if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
7714 raise errors.OpPrereqError("Instance '%s' has faulty disks on"
7715 " node '%s'" % (instance.name, node_name),
7717 except errors.OpPrereqError, err:
7718 if self.op.ignore_consistency:
7719 self.proc.LogWarning(str(err.args[0]))
7723 def CheckPrereq(self):
7724 """Check prerequisites.
7727 storage_type = self.op.storage_type
7729 if (constants.SO_FIX_CONSISTENCY not in
7730 constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
7731 raise errors.OpPrereqError("Storage units of type '%s' can not be"
7732 " repaired" % storage_type,
7735 # Check whether any instance on this node has faulty disks
7736 for inst in _GetNodeInstances(self.cfg, self.op.node_name):
7737 if not inst.admin_up:
7739 check_nodes = set(inst.all_nodes)
7740 check_nodes.discard(self.op.node_name)
7741 for inst_node_name in check_nodes:
7742 self._CheckFaultyDisks(inst, inst_node_name)
7744 def Exec(self, feedback_fn):
7745 feedback_fn("Repairing storage unit '%s' on %s ..." %
7746 (self.op.name, self.op.node_name))
7748 st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
7749 result = self.rpc.call_storage_execute(self.op.node_name,
7750 self.op.storage_type, st_args,
7752 constants.SO_FIX_CONSISTENCY)
7753 result.Raise("Failed to repair storage unit '%s' on %s" %
7754 (self.op.name, self.op.node_name))
7757 class LUNodeEvacuationStrategy(NoHooksLU):
7758 """Computes the node evacuation strategy.
7761 _OP_REQP = ["nodes"]
7764 def CheckArguments(self):
7765 if not hasattr(self.op, "remote_node"):
7766 self.op.remote_node = None
7767 if not hasattr(self.op, "iallocator"):
7768 self.op.iallocator = None
7769 if self.op.remote_node is not None and self.op.iallocator is not None:
7770 raise errors.OpPrereqError("Give either the iallocator or the new"
7771 " secondary, not both", errors.ECODE_INVAL)
7773 def ExpandNames(self):
7774 self.op.nodes = _GetWantedNodes(self, self.op.nodes)
7775 self.needed_locks = locks = {}
7776 if self.op.remote_node is None:
7777 locks[locking.LEVEL_NODE] = locking.ALL_SET
7779 self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
7780 locks[locking.LEVEL_NODE] = self.op.nodes + [self.op.remote_node]
7782 def CheckPrereq(self):
7785 def Exec(self, feedback_fn):
7786 if self.op.remote_node is not None:
7788 for node in self.op.nodes:
7789 instances.extend(_GetNodeSecondaryInstances(self.cfg, node))
7792 if i.primary_node == self.op.remote_node:
7793 raise errors.OpPrereqError("Node %s is the primary node of"
7794 " instance %s, cannot use it as"
7796 (self.op.remote_node, i.name),
7798 result.append([i.name, self.op.remote_node])
7800 ial = IAllocator(self.cfg, self.rpc,
7801 mode=constants.IALLOCATOR_MODE_MEVAC,
7802 evac_nodes=self.op.nodes)
7803 ial.Run(self.op.iallocator, validate=True)
7805 raise errors.OpExecError("No valid evacuation solution: %s" % ial.info,
7811 class LUGrowDisk(LogicalUnit):
7812 """Grow a disk of an instance.
7816 HTYPE = constants.HTYPE_INSTANCE
7817 _OP_REQP = ["instance_name", "disk", "amount", "wait_for_sync"]
7820 def ExpandNames(self):
7821 self._ExpandAndLockInstance()
7822 self.needed_locks[locking.LEVEL_NODE] = []
7823 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7825 def DeclareLocks(self, level):
7826 if level == locking.LEVEL_NODE:
7827 self._LockInstancesNodes()
7829 def BuildHooksEnv(self):
7832 This runs on the master, the primary and all the secondaries.
7836 "DISK": self.op.disk,
7837 "AMOUNT": self.op.amount,
7839 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7840 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7843 def CheckPrereq(self):
7844 """Check prerequisites.
7846 This checks that the instance is in the cluster.
7849 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7850 assert instance is not None, \
7851 "Cannot retrieve locked instance %s" % self.op.instance_name
7852 nodenames = list(instance.all_nodes)
7853 for node in nodenames:
7854 _CheckNodeOnline(self, node)
7857 self.instance = instance
7859 if instance.disk_template not in constants.DTS_GROWABLE:
7860 raise errors.OpPrereqError("Instance's disk layout does not support"
7861 " growing.", errors.ECODE_INVAL)
7863 self.disk = instance.FindDisk(self.op.disk)
7865 if instance.disk_template != constants.DT_FILE:
7866 # TODO: check the free disk space for file, when that feature will be
7868 _CheckNodesFreeDisk(self, nodenames, self.op.amount)
7870 def Exec(self, feedback_fn):
7871 """Execute disk grow.
7874 instance = self.instance
7876 for node in instance.all_nodes:
7877 self.cfg.SetDiskID(disk, node)
7878 result = self.rpc.call_blockdev_grow(node, disk, self.op.amount)
7879 result.Raise("Grow request failed to node %s" % node)
7881 # TODO: Rewrite code to work properly
7882 # DRBD goes into sync mode for a short amount of time after executing the
7883 # "resize" command. DRBD 8.x below version 8.0.13 contains a bug whereby
7884 # calling "resize" in sync mode fails. Sleeping for a short amount of
7885 # time is a work-around.
7888 disk.RecordGrow(self.op.amount)
7889 self.cfg.Update(instance, feedback_fn)
7890 if self.op.wait_for_sync:
7891 disk_abort = not _WaitForSync(self, instance)
7893 self.proc.LogWarning("Warning: disk sync-ing has not returned a good"
7894 " status.\nPlease check the instance.")
7897 class LUQueryInstanceData(NoHooksLU):
7898 """Query runtime instance data.
7901 _OP_REQP = ["instances", "static"]
7904 def ExpandNames(self):
7905 self.needed_locks = {}
7906 self.share_locks = dict.fromkeys(locking.LEVELS, 1)
7908 if not isinstance(self.op.instances, list):
7909 raise errors.OpPrereqError("Invalid argument type 'instances'",
7912 if self.op.instances:
7913 self.wanted_names = []
7914 for name in self.op.instances:
7915 full_name = _ExpandInstanceName(self.cfg, name)
7916 self.wanted_names.append(full_name)
7917 self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
7919 self.wanted_names = None
7920 self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
7922 self.needed_locks[locking.LEVEL_NODE] = []
7923 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7925 def DeclareLocks(self, level):
7926 if level == locking.LEVEL_NODE:
7927 self._LockInstancesNodes()
7929 def CheckPrereq(self):
7930 """Check prerequisites.
7932 This only checks the optional instance list against the existing names.
7935 if self.wanted_names is None:
7936 self.wanted_names = self.acquired_locks[locking.LEVEL_INSTANCE]
7938 self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name
7939 in self.wanted_names]
7942 def _ComputeBlockdevStatus(self, node, instance_name, dev):
7943 """Returns the status of a block device
7946 if self.op.static or not node:
7949 self.cfg.SetDiskID(dev, node)
7951 result = self.rpc.call_blockdev_find(node, dev)
7955 result.Raise("Can't compute disk status for %s" % instance_name)
7957 status = result.payload
7961 return (status.dev_path, status.major, status.minor,
7962 status.sync_percent, status.estimated_time,
7963 status.is_degraded, status.ldisk_status)
7965 def _ComputeDiskStatus(self, instance, snode, dev):
7966 """Compute block device status.
7969 if dev.dev_type in constants.LDS_DRBD:
7970 # we change the snode then (otherwise we use the one passed in)
7971 if dev.logical_id[0] == instance.primary_node:
7972 snode = dev.logical_id[1]
7974 snode = dev.logical_id[0]
7976 dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
7978 dev_sstatus = self._ComputeBlockdevStatus(snode, instance.name, dev)
7981 dev_children = [self._ComputeDiskStatus(instance, snode, child)
7982 for child in dev.children]
7987 "iv_name": dev.iv_name,
7988 "dev_type": dev.dev_type,
7989 "logical_id": dev.logical_id,
7990 "physical_id": dev.physical_id,
7991 "pstatus": dev_pstatus,
7992 "sstatus": dev_sstatus,
7993 "children": dev_children,
8000 def Exec(self, feedback_fn):
8001 """Gather and return data"""
8004 cluster = self.cfg.GetClusterInfo()
8006 for instance in self.wanted_instances:
8007 if not self.op.static:
8008 remote_info = self.rpc.call_instance_info(instance.primary_node,
8010 instance.hypervisor)
8011 remote_info.Raise("Error checking node %s" % instance.primary_node)
8012 remote_info = remote_info.payload
8013 if remote_info and "state" in remote_info:
8016 remote_state = "down"
8019 if instance.admin_up:
8022 config_state = "down"
8024 disks = [self._ComputeDiskStatus(instance, None, device)
8025 for device in instance.disks]
8028 "name": instance.name,
8029 "config_state": config_state,
8030 "run_state": remote_state,
8031 "pnode": instance.primary_node,
8032 "snodes": instance.secondary_nodes,
8034 # this happens to be the same format used for hooks
8035 "nics": _NICListToTuple(self, instance.nics),
8037 "hypervisor": instance.hypervisor,
8038 "network_port": instance.network_port,
8039 "hv_instance": instance.hvparams,
8040 "hv_actual": cluster.FillHV(instance, skip_globals=True),
8041 "be_instance": instance.beparams,
8042 "be_actual": cluster.FillBE(instance),
8043 "serial_no": instance.serial_no,
8044 "mtime": instance.mtime,
8045 "ctime": instance.ctime,
8046 "uuid": instance.uuid,
8049 result[instance.name] = idict
8054 class LUSetInstanceParams(LogicalUnit):
8055 """Modifies an instances's parameters.
8058 HPATH = "instance-modify"
8059 HTYPE = constants.HTYPE_INSTANCE
8060 _OP_REQP = ["instance_name"]
8063 def CheckArguments(self):
8064 if not hasattr(self.op, 'nics'):
8066 if not hasattr(self.op, 'disks'):
8068 if not hasattr(self.op, 'beparams'):
8069 self.op.beparams = {}
8070 if not hasattr(self.op, 'hvparams'):
8071 self.op.hvparams = {}
8072 if not hasattr(self.op, "disk_template"):
8073 self.op.disk_template = None
8074 if not hasattr(self.op, "remote_node"):
8075 self.op.remote_node = None
8076 if not hasattr(self.op, "os_name"):
8077 self.op.os_name = None
8078 if not hasattr(self.op, "force_variant"):
8079 self.op.force_variant = False
8080 self.op.force = getattr(self.op, "force", False)
8081 if not (self.op.nics or self.op.disks or self.op.disk_template or
8082 self.op.hvparams or self.op.beparams or self.op.os_name):
8083 raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
8085 if self.op.hvparams:
8086 _CheckGlobalHvParams(self.op.hvparams)
8090 for disk_op, disk_dict in self.op.disks:
8091 if disk_op == constants.DDM_REMOVE:
8094 elif disk_op == constants.DDM_ADD:
8097 if not isinstance(disk_op, int):
8098 raise errors.OpPrereqError("Invalid disk index", errors.ECODE_INVAL)
8099 if not isinstance(disk_dict, dict):
8100 msg = "Invalid disk value: expected dict, got '%s'" % disk_dict
8101 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
8103 if disk_op == constants.DDM_ADD:
8104 mode = disk_dict.setdefault('mode', constants.DISK_RDWR)
8105 if mode not in constants.DISK_ACCESS_SET:
8106 raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
8108 size = disk_dict.get('size', None)
8110 raise errors.OpPrereqError("Required disk parameter size missing",
8114 except (TypeError, ValueError), err:
8115 raise errors.OpPrereqError("Invalid disk size parameter: %s" %
8116 str(err), errors.ECODE_INVAL)
8117 disk_dict['size'] = size
8119 # modification of disk
8120 if 'size' in disk_dict:
8121 raise errors.OpPrereqError("Disk size change not possible, use"
8122 " grow-disk", errors.ECODE_INVAL)
8124 if disk_addremove > 1:
8125 raise errors.OpPrereqError("Only one disk add or remove operation"
8126 " supported at a time", errors.ECODE_INVAL)
8128 if self.op.disks and self.op.disk_template is not None:
8129 raise errors.OpPrereqError("Disk template conversion and other disk"
8130 " changes not supported at the same time",
8133 if self.op.disk_template:
8134 _CheckDiskTemplate(self.op.disk_template)
8135 if (self.op.disk_template in constants.DTS_NET_MIRROR and
8136 self.op.remote_node is None):
8137 raise errors.OpPrereqError("Changing the disk template to a mirrored"
8138 " one requires specifying a secondary node",
8143 for nic_op, nic_dict in self.op.nics:
8144 if nic_op == constants.DDM_REMOVE:
8147 elif nic_op == constants.DDM_ADD:
8150 if not isinstance(nic_op, int):
8151 raise errors.OpPrereqError("Invalid nic index", errors.ECODE_INVAL)
8152 if not isinstance(nic_dict, dict):
8153 msg = "Invalid nic value: expected dict, got '%s'" % nic_dict
8154 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
8156 # nic_dict should be a dict
8157 nic_ip = nic_dict.get('ip', None)
8158 if nic_ip is not None:
8159 if nic_ip.lower() == constants.VALUE_NONE:
8160 nic_dict['ip'] = None
8162 if not utils.IsValidIP(nic_ip):
8163 raise errors.OpPrereqError("Invalid IP address '%s'" % nic_ip,
8166 nic_bridge = nic_dict.get('bridge', None)
8167 nic_link = nic_dict.get('link', None)
8168 if nic_bridge and nic_link:
8169 raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
8170 " at the same time", errors.ECODE_INVAL)
8171 elif nic_bridge and nic_bridge.lower() == constants.VALUE_NONE:
8172 nic_dict['bridge'] = None
8173 elif nic_link and nic_link.lower() == constants.VALUE_NONE:
8174 nic_dict['link'] = None
8176 if nic_op == constants.DDM_ADD:
8177 nic_mac = nic_dict.get('mac', None)
8179 nic_dict['mac'] = constants.VALUE_AUTO
8181 if 'mac' in nic_dict:
8182 nic_mac = nic_dict['mac']
8183 if nic_mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
8184 nic_mac = utils.NormalizeAndValidateMac(nic_mac)
8186 if nic_op != constants.DDM_ADD and nic_mac == constants.VALUE_AUTO:
8187 raise errors.OpPrereqError("'auto' is not a valid MAC address when"
8188 " modifying an existing nic",
8191 if nic_addremove > 1:
8192 raise errors.OpPrereqError("Only one NIC add or remove operation"
8193 " supported at a time", errors.ECODE_INVAL)
8195 def ExpandNames(self):
8196 self._ExpandAndLockInstance()
8197 self.needed_locks[locking.LEVEL_NODE] = []
8198 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
8200 def DeclareLocks(self, level):
8201 if level == locking.LEVEL_NODE:
8202 self._LockInstancesNodes()
8203 if self.op.disk_template and self.op.remote_node:
8204 self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
8205 self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
8207 def BuildHooksEnv(self):
8210 This runs on the master, primary and secondaries.
8214 if constants.BE_MEMORY in self.be_new:
8215 args['memory'] = self.be_new[constants.BE_MEMORY]
8216 if constants.BE_VCPUS in self.be_new:
8217 args['vcpus'] = self.be_new[constants.BE_VCPUS]
8218 # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
8219 # information at all.
8222 nic_override = dict(self.op.nics)
8223 c_nicparams = self.cluster.nicparams[constants.PP_DEFAULT]
8224 for idx, nic in enumerate(self.instance.nics):
8225 if idx in nic_override:
8226 this_nic_override = nic_override[idx]
8228 this_nic_override = {}
8229 if 'ip' in this_nic_override:
8230 ip = this_nic_override['ip']
8233 if 'mac' in this_nic_override:
8234 mac = this_nic_override['mac']
8237 if idx in self.nic_pnew:
8238 nicparams = self.nic_pnew[idx]
8240 nicparams = objects.FillDict(c_nicparams, nic.nicparams)
8241 mode = nicparams[constants.NIC_MODE]
8242 link = nicparams[constants.NIC_LINK]
8243 args['nics'].append((ip, mac, mode, link))
8244 if constants.DDM_ADD in nic_override:
8245 ip = nic_override[constants.DDM_ADD].get('ip', None)
8246 mac = nic_override[constants.DDM_ADD]['mac']
8247 nicparams = self.nic_pnew[constants.DDM_ADD]
8248 mode = nicparams[constants.NIC_MODE]
8249 link = nicparams[constants.NIC_LINK]
8250 args['nics'].append((ip, mac, mode, link))
8251 elif constants.DDM_REMOVE in nic_override:
8252 del args['nics'][-1]
8254 env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
8255 if self.op.disk_template:
8256 env["NEW_DISK_TEMPLATE"] = self.op.disk_template
8257 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
8261 def _GetUpdatedParams(old_params, update_dict,
8262 default_values, parameter_types):
8263 """Return the new params dict for the given params.
8265 @type old_params: dict
8266 @param old_params: old parameters
8267 @type update_dict: dict
8268 @param update_dict: dict containing new parameter values,
8269 or constants.VALUE_DEFAULT to reset the
8270 parameter to its default value
8271 @type default_values: dict
8272 @param default_values: default values for the filled parameters
8273 @type parameter_types: dict
8274 @param parameter_types: dict mapping target dict keys to types
8275 in constants.ENFORCEABLE_TYPES
8276 @rtype: (dict, dict)
8277 @return: (new_parameters, filled_parameters)
8280 params_copy = copy.deepcopy(old_params)
8281 for key, val in update_dict.iteritems():
8282 if val == constants.VALUE_DEFAULT:
8284 del params_copy[key]
8288 params_copy[key] = val
8289 utils.ForceDictType(params_copy, parameter_types)
8290 params_filled = objects.FillDict(default_values, params_copy)
8291 return (params_copy, params_filled)
8293 def CheckPrereq(self):
8294 """Check prerequisites.
8296 This only checks the instance list against the existing names.
8299 self.force = self.op.force
8301 # checking the new params on the primary/secondary nodes
8303 instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
8304 cluster = self.cluster = self.cfg.GetClusterInfo()
8305 assert self.instance is not None, \
8306 "Cannot retrieve locked instance %s" % self.op.instance_name
8307 pnode = instance.primary_node
8308 nodelist = list(instance.all_nodes)
8310 if self.op.disk_template:
8311 if instance.disk_template == self.op.disk_template:
8312 raise errors.OpPrereqError("Instance already has disk template %s" %
8313 instance.disk_template, errors.ECODE_INVAL)
8315 if (instance.disk_template,
8316 self.op.disk_template) not in self._DISK_CONVERSIONS:
8317 raise errors.OpPrereqError("Unsupported disk template conversion from"
8318 " %s to %s" % (instance.disk_template,
8319 self.op.disk_template),
8321 if self.op.disk_template in constants.DTS_NET_MIRROR:
8322 _CheckNodeOnline(self, self.op.remote_node)
8323 _CheckNodeNotDrained(self, self.op.remote_node)
8324 disks = [{"size": d.size} for d in instance.disks]
8325 required = _ComputeDiskSize(self.op.disk_template, disks)
8326 _CheckNodesFreeDisk(self, [self.op.remote_node], required)
8327 _CheckInstanceDown(self, instance, "cannot change disk template")
8329 # hvparams processing
8330 if self.op.hvparams:
8331 i_hvdict, hv_new = self._GetUpdatedParams(
8332 instance.hvparams, self.op.hvparams,
8333 cluster.hvparams[instance.hypervisor],
8334 constants.HVS_PARAMETER_TYPES)
8336 hypervisor.GetHypervisor(
8337 instance.hypervisor).CheckParameterSyntax(hv_new)
8338 _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
8339 self.hv_new = hv_new # the new actual values
8340 self.hv_inst = i_hvdict # the new dict (without defaults)
8342 self.hv_new = self.hv_inst = {}
8344 # beparams processing
8345 if self.op.beparams:
8346 i_bedict, be_new = self._GetUpdatedParams(
8347 instance.beparams, self.op.beparams,
8348 cluster.beparams[constants.PP_DEFAULT],
8349 constants.BES_PARAMETER_TYPES)
8350 self.be_new = be_new # the new actual values
8351 self.be_inst = i_bedict # the new dict (without defaults)
8353 self.be_new = self.be_inst = {}
8357 if constants.BE_MEMORY in self.op.beparams and not self.force:
8358 mem_check_list = [pnode]
8359 if be_new[constants.BE_AUTO_BALANCE]:
8360 # either we changed auto_balance to yes or it was from before
8361 mem_check_list.extend(instance.secondary_nodes)
8362 instance_info = self.rpc.call_instance_info(pnode, instance.name,
8363 instance.hypervisor)
8364 nodeinfo = self.rpc.call_node_info(mem_check_list, self.cfg.GetVGName(),
8365 instance.hypervisor)
8366 pninfo = nodeinfo[pnode]
8367 msg = pninfo.fail_msg
8369 # Assume the primary node is unreachable and go ahead
8370 self.warn.append("Can't get info from primary node %s: %s" %
8372 elif not isinstance(pninfo.payload.get('memory_free', None), int):
8373 self.warn.append("Node data from primary node %s doesn't contain"
8374 " free memory information" % pnode)
8375 elif instance_info.fail_msg:
8376 self.warn.append("Can't get instance runtime information: %s" %
8377 instance_info.fail_msg)
8379 if instance_info.payload:
8380 current_mem = int(instance_info.payload['memory'])
8382 # Assume instance not running
8383 # (there is a slight race condition here, but it's not very probable,
8384 # and we have no other way to check)
8386 miss_mem = (be_new[constants.BE_MEMORY] - current_mem -
8387 pninfo.payload['memory_free'])
8389 raise errors.OpPrereqError("This change will prevent the instance"
8390 " from starting, due to %d MB of memory"
8391 " missing on its primary node" % miss_mem,
8394 if be_new[constants.BE_AUTO_BALANCE]:
8395 for node, nres in nodeinfo.items():
8396 if node not in instance.secondary_nodes:
8400 self.warn.append("Can't get info from secondary node %s: %s" %
8402 elif not isinstance(nres.payload.get('memory_free', None), int):
8403 self.warn.append("Secondary node %s didn't return free"
8404 " memory information" % node)
8405 elif be_new[constants.BE_MEMORY] > nres.payload['memory_free']:
8406 self.warn.append("Not enough memory to failover instance to"
8407 " secondary node %s" % node)
8412 for nic_op, nic_dict in self.op.nics:
8413 if nic_op == constants.DDM_REMOVE:
8414 if not instance.nics:
8415 raise errors.OpPrereqError("Instance has no NICs, cannot remove",
8418 if nic_op != constants.DDM_ADD:
8420 if not instance.nics:
8421 raise errors.OpPrereqError("Invalid NIC index %s, instance has"
8422 " no NICs" % nic_op,
8424 if nic_op < 0 or nic_op >= len(instance.nics):
8425 raise errors.OpPrereqError("Invalid NIC index %s, valid values"
8427 (nic_op, len(instance.nics) - 1),
8429 old_nic_params = instance.nics[nic_op].nicparams
8430 old_nic_ip = instance.nics[nic_op].ip
8435 update_params_dict = dict([(key, nic_dict[key])
8436 for key in constants.NICS_PARAMETERS
8437 if key in nic_dict])
8439 if 'bridge' in nic_dict:
8440 update_params_dict[constants.NIC_LINK] = nic_dict['bridge']
8442 new_nic_params, new_filled_nic_params = \
8443 self._GetUpdatedParams(old_nic_params, update_params_dict,
8444 cluster.nicparams[constants.PP_DEFAULT],
8445 constants.NICS_PARAMETER_TYPES)
8446 objects.NIC.CheckParameterSyntax(new_filled_nic_params)
8447 self.nic_pinst[nic_op] = new_nic_params
8448 self.nic_pnew[nic_op] = new_filled_nic_params
8449 new_nic_mode = new_filled_nic_params[constants.NIC_MODE]
8451 if new_nic_mode == constants.NIC_MODE_BRIDGED:
8452 nic_bridge = new_filled_nic_params[constants.NIC_LINK]
8453 msg = self.rpc.call_bridges_exist(pnode, [nic_bridge]).fail_msg
8455 msg = "Error checking bridges on node %s: %s" % (pnode, msg)
8457 self.warn.append(msg)
8459 raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
8460 if new_nic_mode == constants.NIC_MODE_ROUTED:
8461 if 'ip' in nic_dict:
8462 nic_ip = nic_dict['ip']
8466 raise errors.OpPrereqError('Cannot set the nic ip to None'
8467 ' on a routed nic', errors.ECODE_INVAL)
8468 if 'mac' in nic_dict:
8469 nic_mac = nic_dict['mac']
8471 raise errors.OpPrereqError('Cannot set the nic mac to None',
8473 elif nic_mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
8474 # otherwise generate the mac
8475 nic_dict['mac'] = self.cfg.GenerateMAC(self.proc.GetECId())
8477 # or validate/reserve the current one
8479 self.cfg.ReserveMAC(nic_mac, self.proc.GetECId())
8480 except errors.ReservationError:
8481 raise errors.OpPrereqError("MAC address %s already in use"
8482 " in cluster" % nic_mac,
8483 errors.ECODE_NOTUNIQUE)
8486 if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
8487 raise errors.OpPrereqError("Disk operations not supported for"
8488 " diskless instances",
8490 for disk_op, _ in self.op.disks:
8491 if disk_op == constants.DDM_REMOVE:
8492 if len(instance.disks) == 1:
8493 raise errors.OpPrereqError("Cannot remove the last disk of"
8494 " an instance", errors.ECODE_INVAL)
8495 _CheckInstanceDown(self, instance, "cannot remove disks")
8497 if (disk_op == constants.DDM_ADD and
8498 len(instance.nics) >= constants.MAX_DISKS):
8499 raise errors.OpPrereqError("Instance has too many disks (%d), cannot"
8500 " add more" % constants.MAX_DISKS,
8502 if disk_op not in (constants.DDM_ADD, constants.DDM_REMOVE):
8504 if disk_op < 0 or disk_op >= len(instance.disks):
8505 raise errors.OpPrereqError("Invalid disk index %s, valid values"
8507 (disk_op, len(instance.disks)),
8511 if self.op.os_name and not self.op.force:
8512 _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
8513 self.op.force_variant)
8517 def _ConvertPlainToDrbd(self, feedback_fn):
8518 """Converts an instance from plain to drbd.
8521 feedback_fn("Converting template to drbd")
8522 instance = self.instance
8523 pnode = instance.primary_node
8524 snode = self.op.remote_node
8526 # create a fake disk info for _GenerateDiskTemplate
8527 disk_info = [{"size": d.size, "mode": d.mode} for d in instance.disks]
8528 new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
8529 instance.name, pnode, [snode],
8530 disk_info, None, None, 0)
8531 info = _GetInstanceInfoText(instance)
8532 feedback_fn("Creating aditional volumes...")
8533 # first, create the missing data and meta devices
8534 for disk in new_disks:
8535 # unfortunately this is... not too nice
8536 _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
8538 for child in disk.children:
8539 _CreateSingleBlockDev(self, snode, instance, child, info, True)
8540 # at this stage, all new LVs have been created, we can rename the
8542 feedback_fn("Renaming original volumes...")
8543 rename_list = [(o, n.children[0].logical_id)
8544 for (o, n) in zip(instance.disks, new_disks)]
8545 result = self.rpc.call_blockdev_rename(pnode, rename_list)
8546 result.Raise("Failed to rename original LVs")
8548 feedback_fn("Initializing DRBD devices...")
8549 # all child devices are in place, we can now create the DRBD devices
8550 for disk in new_disks:
8551 for node in [pnode, snode]:
8552 f_create = node == pnode
8553 _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
8555 # at this point, the instance has been modified
8556 instance.disk_template = constants.DT_DRBD8
8557 instance.disks = new_disks
8558 self.cfg.Update(instance, feedback_fn)
8560 # disks are created, waiting for sync
8561 disk_abort = not _WaitForSync(self, instance)
8563 raise errors.OpExecError("There are some degraded disks for"
8564 " this instance, please cleanup manually")
8566 def _ConvertDrbdToPlain(self, feedback_fn):
8567 """Converts an instance from drbd to plain.
8570 instance = self.instance
8571 assert len(instance.secondary_nodes) == 1
8572 pnode = instance.primary_node
8573 snode = instance.secondary_nodes[0]
8574 feedback_fn("Converting template to plain")
8576 old_disks = instance.disks
8577 new_disks = [d.children[0] for d in old_disks]
8579 # copy over size and mode
8580 for parent, child in zip(old_disks, new_disks):
8581 child.size = parent.size
8582 child.mode = parent.mode
8584 # update instance structure
8585 instance.disks = new_disks
8586 instance.disk_template = constants.DT_PLAIN
8587 self.cfg.Update(instance, feedback_fn)
8589 feedback_fn("Removing volumes on the secondary node...")
8590 for disk in old_disks:
8591 self.cfg.SetDiskID(disk, snode)
8592 msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
8594 self.LogWarning("Could not remove block device %s on node %s,"
8595 " continuing anyway: %s", disk.iv_name, snode, msg)
8597 feedback_fn("Removing unneeded volumes on the primary node...")
8598 for idx, disk in enumerate(old_disks):
8599 meta = disk.children[1]
8600 self.cfg.SetDiskID(meta, pnode)
8601 msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
8603 self.LogWarning("Could not remove metadata for disk %d on node %s,"
8604 " continuing anyway: %s", idx, pnode, msg)
8607 def Exec(self, feedback_fn):
8608 """Modifies an instance.
8610 All parameters take effect only at the next restart of the instance.
8613 # Process here the warnings from CheckPrereq, as we don't have a
8614 # feedback_fn there.
8615 for warn in self.warn:
8616 feedback_fn("WARNING: %s" % warn)
8619 instance = self.instance
8621 for disk_op, disk_dict in self.op.disks:
8622 if disk_op == constants.DDM_REMOVE:
8623 # remove the last disk
8624 device = instance.disks.pop()
8625 device_idx = len(instance.disks)
8626 for node, disk in device.ComputeNodeTree(instance.primary_node):
8627 self.cfg.SetDiskID(disk, node)
8628 msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
8630 self.LogWarning("Could not remove disk/%d on node %s: %s,"
8631 " continuing anyway", device_idx, node, msg)
8632 result.append(("disk/%d" % device_idx, "remove"))
8633 elif disk_op == constants.DDM_ADD:
8635 if instance.disk_template == constants.DT_FILE:
8636 file_driver, file_path = instance.disks[0].logical_id
8637 file_path = os.path.dirname(file_path)
8639 file_driver = file_path = None
8640 disk_idx_base = len(instance.disks)
8641 new_disk = _GenerateDiskTemplate(self,
8642 instance.disk_template,
8643 instance.name, instance.primary_node,
8644 instance.secondary_nodes,
8649 instance.disks.append(new_disk)
8650 info = _GetInstanceInfoText(instance)
8652 logging.info("Creating volume %s for instance %s",
8653 new_disk.iv_name, instance.name)
8654 # Note: this needs to be kept in sync with _CreateDisks
8656 for node in instance.all_nodes:
8657 f_create = node == instance.primary_node
8659 _CreateBlockDev(self, node, instance, new_disk,
8660 f_create, info, f_create)
8661 except errors.OpExecError, err:
8662 self.LogWarning("Failed to create volume %s (%s) on"
8664 new_disk.iv_name, new_disk, node, err)
8665 result.append(("disk/%d" % disk_idx_base, "add:size=%s,mode=%s" %
8666 (new_disk.size, new_disk.mode)))
8668 # change a given disk
8669 instance.disks[disk_op].mode = disk_dict['mode']
8670 result.append(("disk.mode/%d" % disk_op, disk_dict['mode']))
8672 if self.op.disk_template:
8673 r_shut = _ShutdownInstanceDisks(self, instance)
8675 raise errors.OpExecError("Cannot shutdow instance disks, unable to"
8676 " proceed with disk template conversion")
8677 mode = (instance.disk_template, self.op.disk_template)
8679 self._DISK_CONVERSIONS[mode](self, feedback_fn)
8681 self.cfg.ReleaseDRBDMinors(instance.name)
8683 result.append(("disk_template", self.op.disk_template))
8686 for nic_op, nic_dict in self.op.nics:
8687 if nic_op == constants.DDM_REMOVE:
8688 # remove the last nic
8689 del instance.nics[-1]
8690 result.append(("nic.%d" % len(instance.nics), "remove"))
8691 elif nic_op == constants.DDM_ADD:
8692 # mac and bridge should be set, by now
8693 mac = nic_dict['mac']
8694 ip = nic_dict.get('ip', None)
8695 nicparams = self.nic_pinst[constants.DDM_ADD]
8696 new_nic = objects.NIC(mac=mac, ip=ip, nicparams=nicparams)
8697 instance.nics.append(new_nic)
8698 result.append(("nic.%d" % (len(instance.nics) - 1),
8699 "add:mac=%s,ip=%s,mode=%s,link=%s" %
8700 (new_nic.mac, new_nic.ip,
8701 self.nic_pnew[constants.DDM_ADD][constants.NIC_MODE],
8702 self.nic_pnew[constants.DDM_ADD][constants.NIC_LINK]
8705 for key in 'mac', 'ip':
8707 setattr(instance.nics[nic_op], key, nic_dict[key])
8708 if nic_op in self.nic_pinst:
8709 instance.nics[nic_op].nicparams = self.nic_pinst[nic_op]
8710 for key, val in nic_dict.iteritems():
8711 result.append(("nic.%s/%d" % (key, nic_op), val))
8714 if self.op.hvparams:
8715 instance.hvparams = self.hv_inst
8716 for key, val in self.op.hvparams.iteritems():
8717 result.append(("hv/%s" % key, val))
8720 if self.op.beparams:
8721 instance.beparams = self.be_inst
8722 for key, val in self.op.beparams.iteritems():
8723 result.append(("be/%s" % key, val))
8727 instance.os = self.op.os_name
8729 self.cfg.Update(instance, feedback_fn)
8733 _DISK_CONVERSIONS = {
8734 (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
8735 (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
8738 class LUQueryExports(NoHooksLU):
8739 """Query the exports list
8742 _OP_REQP = ['nodes']
8745 def ExpandNames(self):
8746 self.needed_locks = {}
8747 self.share_locks[locking.LEVEL_NODE] = 1
8748 if not self.op.nodes:
8749 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
8751 self.needed_locks[locking.LEVEL_NODE] = \
8752 _GetWantedNodes(self, self.op.nodes)
8754 def CheckPrereq(self):
8755 """Check prerequisites.
8758 self.nodes = self.acquired_locks[locking.LEVEL_NODE]
8760 def Exec(self, feedback_fn):
8761 """Compute the list of all the exported system images.
8764 @return: a dictionary with the structure node->(export-list)
8765 where export-list is a list of the instances exported on
8769 rpcresult = self.rpc.call_export_list(self.nodes)
8771 for node in rpcresult:
8772 if rpcresult[node].fail_msg:
8773 result[node] = False
8775 result[node] = rpcresult[node].payload
8780 class LUExportInstance(LogicalUnit):
8781 """Export an instance to an image in the cluster.
8784 HPATH = "instance-export"
8785 HTYPE = constants.HTYPE_INSTANCE
8786 _OP_REQP = ["instance_name", "target_node", "shutdown"]
8789 def CheckArguments(self):
8790 """Check the arguments.
8793 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
8794 constants.DEFAULT_SHUTDOWN_TIMEOUT)
8796 def ExpandNames(self):
8797 self._ExpandAndLockInstance()
8798 # FIXME: lock only instance primary and destination node
8800 # Sad but true, for now we have do lock all nodes, as we don't know where
8801 # the previous export might be, and and in this LU we search for it and
8802 # remove it from its current node. In the future we could fix this by:
8803 # - making a tasklet to search (share-lock all), then create the new one,
8804 # then one to remove, after
8805 # - removing the removal operation altogether
8806 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
8808 def DeclareLocks(self, level):
8809 """Last minute lock declaration."""
8810 # All nodes are locked anyway, so nothing to do here.
8812 def BuildHooksEnv(self):
8815 This will run on the master, primary node and target node.
8819 "EXPORT_NODE": self.op.target_node,
8820 "EXPORT_DO_SHUTDOWN": self.op.shutdown,
8821 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
8823 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
8824 nl = [self.cfg.GetMasterNode(), self.instance.primary_node,
8825 self.op.target_node]
8828 def CheckPrereq(self):
8829 """Check prerequisites.
8831 This checks that the instance and node names are valid.
8834 instance_name = self.op.instance_name
8835 self.instance = self.cfg.GetInstanceInfo(instance_name)
8836 assert self.instance is not None, \
8837 "Cannot retrieve locked instance %s" % self.op.instance_name
8838 _CheckNodeOnline(self, self.instance.primary_node)
8840 self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
8841 self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
8842 assert self.dst_node is not None
8844 _CheckNodeOnline(self, self.dst_node.name)
8845 _CheckNodeNotDrained(self, self.dst_node.name)
8847 # instance disk type verification
8848 for disk in self.instance.disks:
8849 if disk.dev_type == constants.LD_FILE:
8850 raise errors.OpPrereqError("Export not supported for instances with"
8851 " file-based disks", errors.ECODE_INVAL)
8853 def Exec(self, feedback_fn):
8854 """Export an instance to an image in the cluster.
8857 instance = self.instance
8858 dst_node = self.dst_node
8859 src_node = instance.primary_node
8861 if self.op.shutdown:
8862 # shutdown the instance, but not the disks
8863 feedback_fn("Shutting down instance %s" % instance.name)
8864 result = self.rpc.call_instance_shutdown(src_node, instance,
8865 self.shutdown_timeout)
8866 result.Raise("Could not shutdown instance %s on"
8867 " node %s" % (instance.name, src_node))
8869 vgname = self.cfg.GetVGName()
8873 # set the disks ID correctly since call_instance_start needs the
8874 # correct drbd minor to create the symlinks
8875 for disk in instance.disks:
8876 self.cfg.SetDiskID(disk, src_node)
8878 activate_disks = (not instance.admin_up)
8881 # Activate the instance disks if we'exporting a stopped instance
8882 feedback_fn("Activating disks for %s" % instance.name)
8883 _StartInstanceDisks(self, instance, None)
8889 for idx, disk in enumerate(instance.disks):
8890 feedback_fn("Creating a snapshot of disk/%s on node %s" %
8893 # result.payload will be a snapshot of an lvm leaf of the one we
8895 result = self.rpc.call_blockdev_snapshot(src_node, disk)
8896 msg = result.fail_msg
8898 self.LogWarning("Could not snapshot disk/%s on node %s: %s",
8900 snap_disks.append(False)
8902 disk_id = (vgname, result.payload)
8903 new_dev = objects.Disk(dev_type=constants.LD_LV, size=disk.size,
8904 logical_id=disk_id, physical_id=disk_id,
8905 iv_name=disk.iv_name)
8906 snap_disks.append(new_dev)
8909 if self.op.shutdown and instance.admin_up:
8910 feedback_fn("Starting instance %s" % instance.name)
8911 result = self.rpc.call_instance_start(src_node, instance, None, None)
8912 msg = result.fail_msg
8914 _ShutdownInstanceDisks(self, instance)
8915 raise errors.OpExecError("Could not start instance: %s" % msg)
8917 # TODO: check for size
8919 cluster_name = self.cfg.GetClusterName()
8920 for idx, dev in enumerate(snap_disks):
8921 feedback_fn("Exporting snapshot %s from %s to %s" %
8922 (idx, src_node, dst_node.name))
8924 # FIXME: pass debug from opcode to backend
8925 result = self.rpc.call_snapshot_export(src_node, dev, dst_node.name,
8926 instance, cluster_name,
8927 idx, self.op.debug_level)
8928 msg = result.fail_msg
8930 self.LogWarning("Could not export disk/%s from node %s to"
8931 " node %s: %s", idx, src_node, dst_node.name, msg)
8932 dresults.append(False)
8934 dresults.append(True)
8935 msg = self.rpc.call_blockdev_remove(src_node, dev).fail_msg
8937 self.LogWarning("Could not remove snapshot for disk/%d from node"
8938 " %s: %s", idx, src_node, msg)
8940 dresults.append(False)
8942 feedback_fn("Finalizing export on %s" % dst_node.name)
8943 result = self.rpc.call_finalize_export(dst_node.name, instance,
8946 msg = result.fail_msg
8948 self.LogWarning("Could not finalize export for instance %s"
8949 " on node %s: %s", instance.name, dst_node.name, msg)
8954 feedback_fn("Deactivating disks for %s" % instance.name)
8955 _ShutdownInstanceDisks(self, instance)
8957 nodelist = self.cfg.GetNodeList()
8958 nodelist.remove(dst_node.name)
8960 # on one-node clusters nodelist will be empty after the removal
8961 # if we proceed the backup would be removed because OpQueryExports
8962 # substitutes an empty list with the full cluster node list.
8963 iname = instance.name
8965 feedback_fn("Removing old exports for instance %s" % iname)
8966 exportlist = self.rpc.call_export_list(nodelist)
8967 for node in exportlist:
8968 if exportlist[node].fail_msg:
8970 if iname in exportlist[node].payload:
8971 msg = self.rpc.call_export_remove(node, iname).fail_msg
8973 self.LogWarning("Could not remove older export for instance %s"
8974 " on node %s: %s", iname, node, msg)
8975 return fin_resu, dresults
8978 class LURemoveExport(NoHooksLU):
8979 """Remove exports related to the named instance.
8982 _OP_REQP = ["instance_name"]
8985 def ExpandNames(self):
8986 self.needed_locks = {}
8987 # We need all nodes to be locked in order for RemoveExport to work, but we
8988 # don't need to lock the instance itself, as nothing will happen to it (and
8989 # we can remove exports also for a removed instance)
8990 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
8992 def CheckPrereq(self):
8993 """Check prerequisites.
8997 def Exec(self, feedback_fn):
8998 """Remove any export.
9001 instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
9002 # If the instance was not found we'll try with the name that was passed in.
9003 # This will only work if it was an FQDN, though.
9005 if not instance_name:
9007 instance_name = self.op.instance_name
9009 locked_nodes = self.acquired_locks[locking.LEVEL_NODE]
9010 exportlist = self.rpc.call_export_list(locked_nodes)
9012 for node in exportlist:
9013 msg = exportlist[node].fail_msg
9015 self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
9017 if instance_name in exportlist[node].payload:
9019 result = self.rpc.call_export_remove(node, instance_name)
9020 msg = result.fail_msg
9022 logging.error("Could not remove export for instance %s"
9023 " on node %s: %s", instance_name, node, msg)
9025 if fqdn_warn and not found:
9026 feedback_fn("Export not found. If trying to remove an export belonging"
9027 " to a deleted instance please use its Fully Qualified"
9031 class TagsLU(NoHooksLU): # pylint: disable-msg=W0223
9034 This is an abstract class which is the parent of all the other tags LUs.
9038 def ExpandNames(self):
9039 self.needed_locks = {}
9040 if self.op.kind == constants.TAG_NODE:
9041 self.op.name = _ExpandNodeName(self.cfg, self.op.name)
9042 self.needed_locks[locking.LEVEL_NODE] = self.op.name
9043 elif self.op.kind == constants.TAG_INSTANCE:
9044 self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
9045 self.needed_locks[locking.LEVEL_INSTANCE] = self.op.name
9047 def CheckPrereq(self):
9048 """Check prerequisites.
9051 if self.op.kind == constants.TAG_CLUSTER:
9052 self.target = self.cfg.GetClusterInfo()
9053 elif self.op.kind == constants.TAG_NODE:
9054 self.target = self.cfg.GetNodeInfo(self.op.name)
9055 elif self.op.kind == constants.TAG_INSTANCE:
9056 self.target = self.cfg.GetInstanceInfo(self.op.name)
9058 raise errors.OpPrereqError("Wrong tag type requested (%s)" %
9059 str(self.op.kind), errors.ECODE_INVAL)
9062 class LUGetTags(TagsLU):
9063 """Returns the tags of a given object.
9066 _OP_REQP = ["kind", "name"]
9069 def Exec(self, feedback_fn):
9070 """Returns the tag list.
9073 return list(self.target.GetTags())
9076 class LUSearchTags(NoHooksLU):
9077 """Searches the tags for a given pattern.
9080 _OP_REQP = ["pattern"]
9083 def ExpandNames(self):
9084 self.needed_locks = {}
9086 def CheckPrereq(self):
9087 """Check prerequisites.
9089 This checks the pattern passed for validity by compiling it.
9093 self.re = re.compile(self.op.pattern)
9094 except re.error, err:
9095 raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
9096 (self.op.pattern, err), errors.ECODE_INVAL)
9098 def Exec(self, feedback_fn):
9099 """Returns the tag list.
9103 tgts = [("/cluster", cfg.GetClusterInfo())]
9104 ilist = cfg.GetAllInstancesInfo().values()
9105 tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
9106 nlist = cfg.GetAllNodesInfo().values()
9107 tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
9109 for path, target in tgts:
9110 for tag in target.GetTags():
9111 if self.re.search(tag):
9112 results.append((path, tag))
9116 class LUAddTags(TagsLU):
9117 """Sets a tag on a given object.
9120 _OP_REQP = ["kind", "name", "tags"]
9123 def CheckPrereq(self):
9124 """Check prerequisites.
9126 This checks the type and length of the tag name and value.
9129 TagsLU.CheckPrereq(self)
9130 for tag in self.op.tags:
9131 objects.TaggableObject.ValidateTag(tag)
9133 def Exec(self, feedback_fn):
9138 for tag in self.op.tags:
9139 self.target.AddTag(tag)
9140 except errors.TagError, err:
9141 raise errors.OpExecError("Error while setting tag: %s" % str(err))
9142 self.cfg.Update(self.target, feedback_fn)
9145 class LUDelTags(TagsLU):
9146 """Delete a list of tags from a given object.
9149 _OP_REQP = ["kind", "name", "tags"]
9152 def CheckPrereq(self):
9153 """Check prerequisites.
9155 This checks that we have the given tag.
9158 TagsLU.CheckPrereq(self)
9159 for tag in self.op.tags:
9160 objects.TaggableObject.ValidateTag(tag)
9161 del_tags = frozenset(self.op.tags)
9162 cur_tags = self.target.GetTags()
9163 if not del_tags <= cur_tags:
9164 diff_tags = del_tags - cur_tags
9165 diff_names = ["'%s'" % tag for tag in diff_tags]
9167 raise errors.OpPrereqError("Tag(s) %s not found" %
9168 (",".join(diff_names)), errors.ECODE_NOENT)
9170 def Exec(self, feedback_fn):
9171 """Remove the tag from the object.
9174 for tag in self.op.tags:
9175 self.target.RemoveTag(tag)
9176 self.cfg.Update(self.target, feedback_fn)
9179 class LUTestDelay(NoHooksLU):
9180 """Sleep for a specified amount of time.
9182 This LU sleeps on the master and/or nodes for a specified amount of
9186 _OP_REQP = ["duration", "on_master", "on_nodes"]
9189 def ExpandNames(self):
9190 """Expand names and set required locks.
9192 This expands the node list, if any.
9195 self.needed_locks = {}
9196 if self.op.on_nodes:
9197 # _GetWantedNodes can be used here, but is not always appropriate to use
9198 # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
9200 self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
9201 self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
9203 def CheckPrereq(self):
9204 """Check prerequisites.
9208 def Exec(self, feedback_fn):
9209 """Do the actual sleep.
9212 if self.op.on_master:
9213 if not utils.TestDelay(self.op.duration):
9214 raise errors.OpExecError("Error during master delay test")
9215 if self.op.on_nodes:
9216 result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
9217 for node, node_result in result.items():
9218 node_result.Raise("Failure during rpc call to node %s" % node)
9221 class IAllocator(object):
9222 """IAllocator framework.
9224 An IAllocator instance has three sets of attributes:
9225 - cfg that is needed to query the cluster
9226 - input data (all members of the _KEYS class attribute are required)
9227 - four buffer attributes (in|out_data|text), that represent the
9228 input (to the external script) in text and data structure format,
9229 and the output from it, again in two formats
9230 - the result variables from the script (success, info, nodes) for
9234 # pylint: disable-msg=R0902
9235 # lots of instance attributes
9237 "name", "mem_size", "disks", "disk_template",
9238 "os", "tags", "nics", "vcpus", "hypervisor",
9241 "name", "relocate_from",
9247 def __init__(self, cfg, rpc, mode, **kwargs):
9250 # init buffer variables
9251 self.in_text = self.out_text = self.in_data = self.out_data = None
9252 # init all input fields so that pylint is happy
9254 self.mem_size = self.disks = self.disk_template = None
9255 self.os = self.tags = self.nics = self.vcpus = None
9256 self.hypervisor = None
9257 self.relocate_from = None
9259 self.evac_nodes = None
9261 self.required_nodes = None
9262 # init result fields
9263 self.success = self.info = self.result = None
9264 if self.mode == constants.IALLOCATOR_MODE_ALLOC:
9265 keyset = self._ALLO_KEYS
9266 fn = self._AddNewInstance
9267 elif self.mode == constants.IALLOCATOR_MODE_RELOC:
9268 keyset = self._RELO_KEYS
9269 fn = self._AddRelocateInstance
9270 elif self.mode == constants.IALLOCATOR_MODE_MEVAC:
9271 keyset = self._EVAC_KEYS
9272 fn = self._AddEvacuateNodes
9274 raise errors.ProgrammerError("Unknown mode '%s' passed to the"
9275 " IAllocator" % self.mode)
9277 if key not in keyset:
9278 raise errors.ProgrammerError("Invalid input parameter '%s' to"
9279 " IAllocator" % key)
9280 setattr(self, key, kwargs[key])
9283 if key not in kwargs:
9284 raise errors.ProgrammerError("Missing input parameter '%s' to"
9285 " IAllocator" % key)
9286 self._BuildInputData(fn)
9288 def _ComputeClusterData(self):
9289 """Compute the generic allocator input data.
9291 This is the data that is independent of the actual operation.
9295 cluster_info = cfg.GetClusterInfo()
9298 "version": constants.IALLOCATOR_VERSION,
9299 "cluster_name": cfg.GetClusterName(),
9300 "cluster_tags": list(cluster_info.GetTags()),
9301 "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
9302 # we don't have job IDs
9304 iinfo = cfg.GetAllInstancesInfo().values()
9305 i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
9309 node_list = cfg.GetNodeList()
9311 if self.mode == constants.IALLOCATOR_MODE_ALLOC:
9312 hypervisor_name = self.hypervisor
9313 elif self.mode == constants.IALLOCATOR_MODE_RELOC:
9314 hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
9315 elif self.mode == constants.IALLOCATOR_MODE_MEVAC:
9316 hypervisor_name = cluster_info.enabled_hypervisors[0]
9318 node_data = self.rpc.call_node_info(node_list, cfg.GetVGName(),
9321 self.rpc.call_all_instances_info(node_list,
9322 cluster_info.enabled_hypervisors)
9323 for nname, nresult in node_data.items():
9324 # first fill in static (config-based) values
9325 ninfo = cfg.GetNodeInfo(nname)
9327 "tags": list(ninfo.GetTags()),
9328 "primary_ip": ninfo.primary_ip,
9329 "secondary_ip": ninfo.secondary_ip,
9330 "offline": ninfo.offline,
9331 "drained": ninfo.drained,
9332 "master_candidate": ninfo.master_candidate,
9335 if not (ninfo.offline or ninfo.drained):
9336 nresult.Raise("Can't get data for node %s" % nname)
9337 node_iinfo[nname].Raise("Can't get node instance info from node %s" %
9339 remote_info = nresult.payload
9341 for attr in ['memory_total', 'memory_free', 'memory_dom0',
9342 'vg_size', 'vg_free', 'cpu_total']:
9343 if attr not in remote_info:
9344 raise errors.OpExecError("Node '%s' didn't return attribute"
9345 " '%s'" % (nname, attr))
9346 if not isinstance(remote_info[attr], int):
9347 raise errors.OpExecError("Node '%s' returned invalid value"
9349 (nname, attr, remote_info[attr]))
9350 # compute memory used by primary instances
9351 i_p_mem = i_p_up_mem = 0
9352 for iinfo, beinfo in i_list:
9353 if iinfo.primary_node == nname:
9354 i_p_mem += beinfo[constants.BE_MEMORY]
9355 if iinfo.name not in node_iinfo[nname].payload:
9358 i_used_mem = int(node_iinfo[nname].payload[iinfo.name]['memory'])
9359 i_mem_diff = beinfo[constants.BE_MEMORY] - i_used_mem
9360 remote_info['memory_free'] -= max(0, i_mem_diff)
9363 i_p_up_mem += beinfo[constants.BE_MEMORY]
9365 # compute memory used by instances
9367 "total_memory": remote_info['memory_total'],
9368 "reserved_memory": remote_info['memory_dom0'],
9369 "free_memory": remote_info['memory_free'],
9370 "total_disk": remote_info['vg_size'],
9371 "free_disk": remote_info['vg_free'],
9372 "total_cpus": remote_info['cpu_total'],
9373 "i_pri_memory": i_p_mem,
9374 "i_pri_up_memory": i_p_up_mem,
9378 node_results[nname] = pnr
9379 data["nodes"] = node_results
9383 for iinfo, beinfo in i_list:
9385 for nic in iinfo.nics:
9386 filled_params = objects.FillDict(
9387 cluster_info.nicparams[constants.PP_DEFAULT],
9389 nic_dict = {"mac": nic.mac,
9391 "mode": filled_params[constants.NIC_MODE],
9392 "link": filled_params[constants.NIC_LINK],
9394 if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
9395 nic_dict["bridge"] = filled_params[constants.NIC_LINK]
9396 nic_data.append(nic_dict)
9398 "tags": list(iinfo.GetTags()),
9399 "admin_up": iinfo.admin_up,
9400 "vcpus": beinfo[constants.BE_VCPUS],
9401 "memory": beinfo[constants.BE_MEMORY],
9403 "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
9405 "disks": [{"size": dsk.size, "mode": dsk.mode} for dsk in iinfo.disks],
9406 "disk_template": iinfo.disk_template,
9407 "hypervisor": iinfo.hypervisor,
9409 pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
9411 instance_data[iinfo.name] = pir
9413 data["instances"] = instance_data
9417 def _AddNewInstance(self):
9418 """Add new instance data to allocator structure.
9420 This in combination with _AllocatorGetClusterData will create the
9421 correct structure needed as input for the allocator.
9423 The checks for the completeness of the opcode must have already been
9427 disk_space = _ComputeDiskSize(self.disk_template, self.disks)
9429 if self.disk_template in constants.DTS_NET_MIRROR:
9430 self.required_nodes = 2
9432 self.required_nodes = 1
9435 "disk_template": self.disk_template,
9438 "vcpus": self.vcpus,
9439 "memory": self.mem_size,
9440 "disks": self.disks,
9441 "disk_space_total": disk_space,
9443 "required_nodes": self.required_nodes,
9447 def _AddRelocateInstance(self):
9448 """Add relocate instance data to allocator structure.
9450 This in combination with _IAllocatorGetClusterData will create the
9451 correct structure needed as input for the allocator.
9453 The checks for the completeness of the opcode must have already been
9457 instance = self.cfg.GetInstanceInfo(self.name)
9458 if instance is None:
9459 raise errors.ProgrammerError("Unknown instance '%s' passed to"
9460 " IAllocator" % self.name)
9462 if instance.disk_template not in constants.DTS_NET_MIRROR:
9463 raise errors.OpPrereqError("Can't relocate non-mirrored instances",
9466 if len(instance.secondary_nodes) != 1:
9467 raise errors.OpPrereqError("Instance has not exactly one secondary node",
9470 self.required_nodes = 1
9471 disk_sizes = [{'size': disk.size} for disk in instance.disks]
9472 disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
9476 "disk_space_total": disk_space,
9477 "required_nodes": self.required_nodes,
9478 "relocate_from": self.relocate_from,
9482 def _AddEvacuateNodes(self):
9483 """Add evacuate nodes data to allocator structure.
9487 "evac_nodes": self.evac_nodes
9491 def _BuildInputData(self, fn):
9492 """Build input data structures.
9495 self._ComputeClusterData()
9498 request["type"] = self.mode
9499 self.in_data["request"] = request
9501 self.in_text = serializer.Dump(self.in_data)
9503 def Run(self, name, validate=True, call_fn=None):
9504 """Run an instance allocator and return the results.
9508 call_fn = self.rpc.call_iallocator_runner
9510 result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
9511 result.Raise("Failure while running the iallocator script")
9513 self.out_text = result.payload
9515 self._ValidateResult()
9517 def _ValidateResult(self):
9518 """Process the allocator results.
9520 This will process and if successful save the result in
9521 self.out_data and the other parameters.
9525 rdict = serializer.Load(self.out_text)
9526 except Exception, err:
9527 raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
9529 if not isinstance(rdict, dict):
9530 raise errors.OpExecError("Can't parse iallocator results: not a dict")
9532 # TODO: remove backwards compatiblity in later versions
9533 if "nodes" in rdict and "result" not in rdict:
9534 rdict["result"] = rdict["nodes"]
9537 for key in "success", "info", "result":
9538 if key not in rdict:
9539 raise errors.OpExecError("Can't parse iallocator results:"
9540 " missing key '%s'" % key)
9541 setattr(self, key, rdict[key])
9543 if not isinstance(rdict["result"], list):
9544 raise errors.OpExecError("Can't parse iallocator results: 'result' key"
9546 self.out_data = rdict
9549 class LUTestAllocator(NoHooksLU):
9550 """Run allocator tests.
9552 This LU runs the allocator tests
9555 _OP_REQP = ["direction", "mode", "name"]
9557 def CheckPrereq(self):
9558 """Check prerequisites.
9560 This checks the opcode parameters depending on the director and mode test.
9563 if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
9564 for attr in ["name", "mem_size", "disks", "disk_template",
9565 "os", "tags", "nics", "vcpus"]:
9566 if not hasattr(self.op, attr):
9567 raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
9568 attr, errors.ECODE_INVAL)
9569 iname = self.cfg.ExpandInstanceName(self.op.name)
9570 if iname is not None:
9571 raise errors.OpPrereqError("Instance '%s' already in the cluster" %
9572 iname, errors.ECODE_EXISTS)
9573 if not isinstance(self.op.nics, list):
9574 raise errors.OpPrereqError("Invalid parameter 'nics'",
9576 for row in self.op.nics:
9577 if (not isinstance(row, dict) or
9580 "bridge" not in row):
9581 raise errors.OpPrereqError("Invalid contents of the 'nics'"
9582 " parameter", errors.ECODE_INVAL)
9583 if not isinstance(self.op.disks, list):
9584 raise errors.OpPrereqError("Invalid parameter 'disks'",
9586 for row in self.op.disks:
9587 if (not isinstance(row, dict) or
9588 "size" not in row or
9589 not isinstance(row["size"], int) or
9590 "mode" not in row or
9591 row["mode"] not in ['r', 'w']):
9592 raise errors.OpPrereqError("Invalid contents of the 'disks'"
9593 " parameter", errors.ECODE_INVAL)
9594 if not hasattr(self.op, "hypervisor") or self.op.hypervisor is None:
9595 self.op.hypervisor = self.cfg.GetHypervisorType()
9596 elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
9597 if not hasattr(self.op, "name"):
9598 raise errors.OpPrereqError("Missing attribute 'name' on opcode input",
9600 fname = _ExpandInstanceName(self.cfg, self.op.name)
9601 self.op.name = fname
9602 self.relocate_from = self.cfg.GetInstanceInfo(fname).secondary_nodes
9603 elif self.op.mode == constants.IALLOCATOR_MODE_MEVAC:
9604 if not hasattr(self.op, "evac_nodes"):
9605 raise errors.OpPrereqError("Missing attribute 'evac_nodes' on"
9606 " opcode input", errors.ECODE_INVAL)
9608 raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
9609 self.op.mode, errors.ECODE_INVAL)
9611 if self.op.direction == constants.IALLOCATOR_DIR_OUT:
9612 if not hasattr(self.op, "allocator") or self.op.allocator is None:
9613 raise errors.OpPrereqError("Missing allocator name",
9615 elif self.op.direction != constants.IALLOCATOR_DIR_IN:
9616 raise errors.OpPrereqError("Wrong allocator test '%s'" %
9617 self.op.direction, errors.ECODE_INVAL)
9619 def Exec(self, feedback_fn):
9620 """Run the allocator test.
9623 if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
9624 ial = IAllocator(self.cfg, self.rpc,
9627 mem_size=self.op.mem_size,
9628 disks=self.op.disks,
9629 disk_template=self.op.disk_template,
9633 vcpus=self.op.vcpus,
9634 hypervisor=self.op.hypervisor,
9636 elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
9637 ial = IAllocator(self.cfg, self.rpc,
9640 relocate_from=list(self.relocate_from),
9642 elif self.op.mode == constants.IALLOCATOR_MODE_MEVAC:
9643 ial = IAllocator(self.cfg, self.rpc,
9645 evac_nodes=self.op.evac_nodes)
9647 raise errors.ProgrammerError("Uncatched mode %s in"
9648 " LUTestAllocator.Exec", self.op.mode)
9650 if self.op.direction == constants.IALLOCATOR_DIR_IN:
9651 result = ial.in_text
9653 ial.Run(self.op.allocator, validate=False)
9654 result = ial.out_text