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
48 from ganeti import compat
49 from ganeti import masterd
51 import ganeti.masterd.instance # pylint: disable-msg=W0611
54 class LogicalUnit(object):
55 """Logical Unit base class.
57 Subclasses must follow these rules:
58 - implement ExpandNames
59 - implement CheckPrereq (except when tasklets are used)
60 - implement Exec (except when tasklets are used)
61 - implement BuildHooksEnv
62 - redefine HPATH and HTYPE
63 - optionally redefine their run requirements:
64 REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
66 Note that all commands require root permissions.
68 @ivar dry_run_result: the value (if any) that will be returned to the caller
69 in dry-run mode (signalled by opcode dry_run parameter)
77 def __init__(self, processor, op, context, rpc):
78 """Constructor for LogicalUnit.
80 This needs to be overridden in derived classes in order to check op
86 self.cfg = context.cfg
87 self.context = context
89 # Dicts used to declare locking needs to mcpu
90 self.needed_locks = None
91 self.acquired_locks = {}
92 self.share_locks = dict.fromkeys(locking.LEVELS, 0)
94 self.remove_locks = {}
95 # Used to force good behavior when calling helper functions
96 self.recalculate_locks = {}
99 self.LogWarning = processor.LogWarning # pylint: disable-msg=C0103
100 self.LogInfo = processor.LogInfo # pylint: disable-msg=C0103
101 self.LogStep = processor.LogStep # pylint: disable-msg=C0103
102 # support for dry-run
103 self.dry_run_result = None
104 # support for generic debug attribute
105 if (not hasattr(self.op, "debug_level") or
106 not isinstance(self.op.debug_level, int)):
107 self.op.debug_level = 0
112 for attr_name in self._OP_REQP:
113 attr_val = getattr(op, attr_name, None)
115 raise errors.OpPrereqError("Required parameter '%s' missing" %
116 attr_name, errors.ECODE_INVAL)
118 self.CheckArguments()
121 """Returns the SshRunner object
125 self.__ssh = ssh.SshRunner(self.cfg.GetClusterName())
128 ssh = property(fget=__GetSSH)
130 def CheckArguments(self):
131 """Check syntactic validity for the opcode arguments.
133 This method is for doing a simple syntactic check and ensure
134 validity of opcode parameters, without any cluster-related
135 checks. While the same can be accomplished in ExpandNames and/or
136 CheckPrereq, doing these separate is better because:
138 - ExpandNames is left as as purely a lock-related function
139 - CheckPrereq is run after we have acquired locks (and possible
142 The function is allowed to change the self.op attribute so that
143 later methods can no longer worry about missing parameters.
148 def ExpandNames(self):
149 """Expand names for this LU.
151 This method is called before starting to execute the opcode, and it should
152 update all the parameters of the opcode to their canonical form (e.g. a
153 short node name must be fully expanded after this method has successfully
154 completed). This way locking, hooks, logging, ecc. can work correctly.
156 LUs which implement this method must also populate the self.needed_locks
157 member, as a dict with lock levels as keys, and a list of needed lock names
160 - use an empty dict if you don't need any lock
161 - if you don't need any lock at a particular level omit that level
162 - don't put anything for the BGL level
163 - if you want all locks at a level use locking.ALL_SET as a value
165 If you need to share locks (rather than acquire them exclusively) at one
166 level you can modify self.share_locks, setting a true value (usually 1) for
167 that level. By default locks are not shared.
169 This function can also define a list of tasklets, which then will be
170 executed in order instead of the usual LU-level CheckPrereq and Exec
171 functions, if those are not defined by the LU.
175 # Acquire all nodes and one instance
176 self.needed_locks = {
177 locking.LEVEL_NODE: locking.ALL_SET,
178 locking.LEVEL_INSTANCE: ['instance1.example.tld'],
180 # Acquire just two nodes
181 self.needed_locks = {
182 locking.LEVEL_NODE: ['node1.example.tld', 'node2.example.tld'],
185 self.needed_locks = {} # No, you can't leave it to the default value None
188 # The implementation of this method is mandatory only if the new LU is
189 # concurrent, so that old LUs don't need to be changed all at the same
192 self.needed_locks = {} # Exclusive LUs don't need locks.
194 raise NotImplementedError
196 def DeclareLocks(self, level):
197 """Declare LU locking needs for a level
199 While most LUs can just declare their locking needs at ExpandNames time,
200 sometimes there's the need to calculate some locks after having acquired
201 the ones before. This function is called just before acquiring locks at a
202 particular level, but after acquiring the ones at lower levels, and permits
203 such calculations. It can be used to modify self.needed_locks, and by
204 default it does nothing.
206 This function is only called if you have something already set in
207 self.needed_locks for the level.
209 @param level: Locking level which is going to be locked
210 @type level: member of ganeti.locking.LEVELS
214 def CheckPrereq(self):
215 """Check prerequisites for this LU.
217 This method should check that the prerequisites for the execution
218 of this LU are fulfilled. It can do internode communication, but
219 it should be idempotent - no cluster or system changes are
222 The method should raise errors.OpPrereqError in case something is
223 not fulfilled. Its return value is ignored.
225 This method should also update all the parameters of the opcode to
226 their canonical form if it hasn't been done by ExpandNames before.
229 if self.tasklets is not None:
230 for (idx, tl) in enumerate(self.tasklets):
231 logging.debug("Checking prerequisites for tasklet %s/%s",
232 idx + 1, len(self.tasklets))
235 raise NotImplementedError
237 def Exec(self, feedback_fn):
240 This method should implement the actual work. It should raise
241 errors.OpExecError for failures that are somewhat dealt with in
245 if self.tasklets is not None:
246 for (idx, tl) in enumerate(self.tasklets):
247 logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
250 raise NotImplementedError
252 def BuildHooksEnv(self):
253 """Build hooks environment for this LU.
255 This method should return a three-node tuple consisting of: a dict
256 containing the environment that will be used for running the
257 specific hook for this LU, a list of node names on which the hook
258 should run before the execution, and a list of node names on which
259 the hook should run after the execution.
261 The keys of the dict must not have 'GANETI_' prefixed as this will
262 be handled in the hooks runner. Also note additional keys will be
263 added by the hooks runner. If the LU doesn't define any
264 environment, an empty dict (and not None) should be returned.
266 No nodes should be returned as an empty list (and not None).
268 Note that if the HPATH for a LU class is None, this function will
272 raise NotImplementedError
274 def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
275 """Notify the LU about the results of its hooks.
277 This method is called every time a hooks phase is executed, and notifies
278 the Logical Unit about the hooks' result. The LU can then use it to alter
279 its result based on the hooks. By default the method does nothing and the
280 previous result is passed back unchanged but any LU can define it if it
281 wants to use the local cluster hook-scripts somehow.
283 @param phase: one of L{constants.HOOKS_PHASE_POST} or
284 L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
285 @param hook_results: the results of the multi-node hooks rpc call
286 @param feedback_fn: function used send feedback back to the caller
287 @param lu_result: the previous Exec result this LU had, or None
289 @return: the new Exec result, based on the previous result
293 # API must be kept, thus we ignore the unused argument and could
294 # be a function warnings
295 # pylint: disable-msg=W0613,R0201
298 def _ExpandAndLockInstance(self):
299 """Helper function to expand and lock an instance.
301 Many LUs that work on an instance take its name in self.op.instance_name
302 and need to expand it and then declare the expanded name for locking. This
303 function does it, and then updates self.op.instance_name to the expanded
304 name. It also initializes needed_locks as a dict, if this hasn't been done
308 if self.needed_locks is None:
309 self.needed_locks = {}
311 assert locking.LEVEL_INSTANCE not in self.needed_locks, \
312 "_ExpandAndLockInstance called with instance-level locks set"
313 self.op.instance_name = _ExpandInstanceName(self.cfg,
314 self.op.instance_name)
315 self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
317 def _LockInstancesNodes(self, primary_only=False):
318 """Helper function to declare instances' nodes for locking.
320 This function should be called after locking one or more instances to lock
321 their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
322 with all primary or secondary nodes for instances already locked and
323 present in self.needed_locks[locking.LEVEL_INSTANCE].
325 It should be called from DeclareLocks, and for safety only works if
326 self.recalculate_locks[locking.LEVEL_NODE] is set.
328 In the future it may grow parameters to just lock some instance's nodes, or
329 to just lock primaries or secondary nodes, if needed.
331 If should be called in DeclareLocks in a way similar to::
333 if level == locking.LEVEL_NODE:
334 self._LockInstancesNodes()
336 @type primary_only: boolean
337 @param primary_only: only lock primary nodes of locked instances
340 assert locking.LEVEL_NODE in self.recalculate_locks, \
341 "_LockInstancesNodes helper function called with no nodes to recalculate"
343 # TODO: check if we're really been called with the instance locks held
345 # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
346 # future we might want to have different behaviors depending on the value
347 # of self.recalculate_locks[locking.LEVEL_NODE]
349 for instance_name in self.acquired_locks[locking.LEVEL_INSTANCE]:
350 instance = self.context.cfg.GetInstanceInfo(instance_name)
351 wanted_nodes.append(instance.primary_node)
353 wanted_nodes.extend(instance.secondary_nodes)
355 if self.recalculate_locks[locking.LEVEL_NODE] == constants.LOCKS_REPLACE:
356 self.needed_locks[locking.LEVEL_NODE] = wanted_nodes
357 elif self.recalculate_locks[locking.LEVEL_NODE] == constants.LOCKS_APPEND:
358 self.needed_locks[locking.LEVEL_NODE].extend(wanted_nodes)
360 del self.recalculate_locks[locking.LEVEL_NODE]
363 class NoHooksLU(LogicalUnit): # pylint: disable-msg=W0223
364 """Simple LU which runs no hooks.
366 This LU is intended as a parent for other LogicalUnits which will
367 run no hooks, in order to reduce duplicate code.
373 def BuildHooksEnv(self):
374 """Empty BuildHooksEnv for NoHooksLu.
376 This just raises an error.
379 assert False, "BuildHooksEnv called for NoHooksLUs"
383 """Tasklet base class.
385 Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
386 they can mix legacy code with tasklets. Locking needs to be done in the LU,
387 tasklets know nothing about locks.
389 Subclasses must follow these rules:
390 - Implement CheckPrereq
394 def __init__(self, lu):
401 def CheckPrereq(self):
402 """Check prerequisites for this tasklets.
404 This method should check whether the prerequisites for the execution of
405 this tasklet are fulfilled. It can do internode communication, but it
406 should be idempotent - no cluster or system changes are allowed.
408 The method should raise errors.OpPrereqError in case something is not
409 fulfilled. Its return value is ignored.
411 This method should also update all parameters to their canonical form if it
412 hasn't been done before.
415 raise NotImplementedError
417 def Exec(self, feedback_fn):
418 """Execute the tasklet.
420 This method should implement the actual work. It should raise
421 errors.OpExecError for failures that are somewhat dealt with in code, or
425 raise NotImplementedError
428 def _GetWantedNodes(lu, nodes):
429 """Returns list of checked and expanded node names.
431 @type lu: L{LogicalUnit}
432 @param lu: the logical unit on whose behalf we execute
434 @param nodes: list of node names or None for all nodes
436 @return: the list of nodes, sorted
437 @raise errors.ProgrammerError: if the nodes parameter is wrong type
440 if not isinstance(nodes, list):
441 raise errors.OpPrereqError("Invalid argument type 'nodes'",
445 raise errors.ProgrammerError("_GetWantedNodes should only be called with a"
446 " non-empty list of nodes whose name is to be expanded.")
448 wanted = [_ExpandNodeName(lu.cfg, name) for name in nodes]
449 return utils.NiceSort(wanted)
452 def _GetWantedInstances(lu, instances):
453 """Returns list of checked and expanded instance names.
455 @type lu: L{LogicalUnit}
456 @param lu: the logical unit on whose behalf we execute
457 @type instances: list
458 @param instances: list of instance names or None for all instances
460 @return: the list of instances, sorted
461 @raise errors.OpPrereqError: if the instances parameter is wrong type
462 @raise errors.OpPrereqError: if any of the passed instances is not found
465 if not isinstance(instances, list):
466 raise errors.OpPrereqError("Invalid argument type 'instances'",
470 wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
472 wanted = utils.NiceSort(lu.cfg.GetInstanceList())
476 def _CheckOutputFields(static, dynamic, selected):
477 """Checks whether all selected fields are valid.
479 @type static: L{utils.FieldSet}
480 @param static: static fields set
481 @type dynamic: L{utils.FieldSet}
482 @param dynamic: dynamic fields set
489 delta = f.NonMatching(selected)
491 raise errors.OpPrereqError("Unknown output fields selected: %s"
492 % ",".join(delta), errors.ECODE_INVAL)
495 def _CheckBooleanOpField(op, name):
496 """Validates boolean opcode parameters.
498 This will ensure that an opcode parameter is either a boolean value,
499 or None (but that it always exists).
502 val = getattr(op, name, None)
503 if not (val is None or isinstance(val, bool)):
504 raise errors.OpPrereqError("Invalid boolean parameter '%s' (%s)" %
505 (name, str(val)), errors.ECODE_INVAL)
506 setattr(op, name, val)
509 def _CheckGlobalHvParams(params):
510 """Validates that given hypervisor params are not global ones.
512 This will ensure that instances don't get customised versions of
516 used_globals = constants.HVC_GLOBALS.intersection(params)
518 msg = ("The following hypervisor parameters are global and cannot"
519 " be customized at instance level, please modify them at"
520 " cluster level: %s" % utils.CommaJoin(used_globals))
521 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
524 def _CheckNodeOnline(lu, node):
525 """Ensure that a given node is online.
527 @param lu: the LU on behalf of which we make the check
528 @param node: the node to check
529 @raise errors.OpPrereqError: if the node is offline
532 if lu.cfg.GetNodeInfo(node).offline:
533 raise errors.OpPrereqError("Can't use offline node %s" % node,
537 def _CheckNodeNotDrained(lu, node):
538 """Ensure that a given node is not drained.
540 @param lu: the LU on behalf of which we make the check
541 @param node: the node to check
542 @raise errors.OpPrereqError: if the node is drained
545 if lu.cfg.GetNodeInfo(node).drained:
546 raise errors.OpPrereqError("Can't use drained node %s" % node,
550 def _CheckNodeHasOS(lu, node, os_name, force_variant):
551 """Ensure that a node supports a given OS.
553 @param lu: the LU on behalf of which we make the check
554 @param node: the node to check
555 @param os_name: the OS to query about
556 @param force_variant: whether to ignore variant errors
557 @raise errors.OpPrereqError: if the node is not supporting the OS
560 result = lu.rpc.call_os_get(node, os_name)
561 result.Raise("OS '%s' not in supported OS list for node %s" %
563 prereq=True, ecode=errors.ECODE_INVAL)
564 if not force_variant:
565 _CheckOSVariant(result.payload, os_name)
568 def _RequireFileStorage():
569 """Checks that file storage is enabled.
571 @raise errors.OpPrereqError: when file storage is disabled
574 if not constants.ENABLE_FILE_STORAGE:
575 raise errors.OpPrereqError("File storage disabled at configure time",
579 def _CheckDiskTemplate(template):
580 """Ensure a given disk template is valid.
583 if template not in constants.DISK_TEMPLATES:
584 msg = ("Invalid disk template name '%s', valid templates are: %s" %
585 (template, utils.CommaJoin(constants.DISK_TEMPLATES)))
586 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
587 if template == constants.DT_FILE:
588 _RequireFileStorage()
591 def _CheckStorageType(storage_type):
592 """Ensure a given storage type is valid.
595 if storage_type not in constants.VALID_STORAGE_TYPES:
596 raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
598 if storage_type == constants.ST_FILE:
599 _RequireFileStorage()
602 def _GetClusterDomainSecret():
603 """Reads the cluster domain secret.
606 return utils.ReadOneLineFile(constants.CLUSTER_DOMAIN_SECRET_FILE,
610 def _CheckInstanceDown(lu, instance, reason):
611 """Ensure that an instance is not running."""
612 if instance.admin_up:
613 raise errors.OpPrereqError("Instance %s is marked to be up, %s" %
614 (instance.name, reason), errors.ECODE_STATE)
616 pnode = instance.primary_node
617 ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
618 ins_l.Raise("Can't contact node %s for instance information" % pnode,
619 prereq=True, ecode=errors.ECODE_ENVIRON)
621 if instance.name in ins_l.payload:
622 raise errors.OpPrereqError("Instance %s is running, %s" %
623 (instance.name, reason), errors.ECODE_STATE)
626 def _ExpandItemName(fn, name, kind):
627 """Expand an item name.
629 @param fn: the function to use for expansion
630 @param name: requested item name
631 @param kind: text description ('Node' or 'Instance')
632 @return: the resolved (full) name
633 @raise errors.OpPrereqError: if the item is not found
637 if full_name is None:
638 raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
643 def _ExpandNodeName(cfg, name):
644 """Wrapper over L{_ExpandItemName} for nodes."""
645 return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
648 def _ExpandInstanceName(cfg, name):
649 """Wrapper over L{_ExpandItemName} for instance."""
650 return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
653 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
654 memory, vcpus, nics, disk_template, disks,
655 bep, hvp, hypervisor_name):
656 """Builds instance related env variables for hooks
658 This builds the hook environment from individual variables.
661 @param name: the name of the instance
662 @type primary_node: string
663 @param primary_node: the name of the instance's primary node
664 @type secondary_nodes: list
665 @param secondary_nodes: list of secondary nodes as strings
666 @type os_type: string
667 @param os_type: the name of the instance's OS
668 @type status: boolean
669 @param status: the should_run status of the instance
671 @param memory: the memory size of the instance
673 @param vcpus: the count of VCPUs the instance has
675 @param nics: list of tuples (ip, mac, mode, link) representing
676 the NICs the instance has
677 @type disk_template: string
678 @param disk_template: the disk template of the instance
680 @param disks: the list of (size, mode) pairs
682 @param bep: the backend parameters for the instance
684 @param hvp: the hypervisor parameters for the instance
685 @type hypervisor_name: string
686 @param hypervisor_name: the hypervisor for the instance
688 @return: the hook environment for this instance
697 "INSTANCE_NAME": name,
698 "INSTANCE_PRIMARY": primary_node,
699 "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
700 "INSTANCE_OS_TYPE": os_type,
701 "INSTANCE_STATUS": str_status,
702 "INSTANCE_MEMORY": memory,
703 "INSTANCE_VCPUS": vcpus,
704 "INSTANCE_DISK_TEMPLATE": disk_template,
705 "INSTANCE_HYPERVISOR": hypervisor_name,
709 nic_count = len(nics)
710 for idx, (ip, mac, mode, link) in enumerate(nics):
713 env["INSTANCE_NIC%d_IP" % idx] = ip
714 env["INSTANCE_NIC%d_MAC" % idx] = mac
715 env["INSTANCE_NIC%d_MODE" % idx] = mode
716 env["INSTANCE_NIC%d_LINK" % idx] = link
717 if mode == constants.NIC_MODE_BRIDGED:
718 env["INSTANCE_NIC%d_BRIDGE" % idx] = link
722 env["INSTANCE_NIC_COUNT"] = nic_count
725 disk_count = len(disks)
726 for idx, (size, mode) in enumerate(disks):
727 env["INSTANCE_DISK%d_SIZE" % idx] = size
728 env["INSTANCE_DISK%d_MODE" % idx] = mode
732 env["INSTANCE_DISK_COUNT"] = disk_count
734 for source, kind in [(bep, "BE"), (hvp, "HV")]:
735 for key, value in source.items():
736 env["INSTANCE_%s_%s" % (kind, key)] = value
741 def _NICListToTuple(lu, nics):
742 """Build a list of nic information tuples.
744 This list is suitable to be passed to _BuildInstanceHookEnv or as a return
745 value in LUQueryInstanceData.
747 @type lu: L{LogicalUnit}
748 @param lu: the logical unit on whose behalf we execute
749 @type nics: list of L{objects.NIC}
750 @param nics: list of nics to convert to hooks tuples
754 c_nicparams = lu.cfg.GetClusterInfo().nicparams[constants.PP_DEFAULT]
758 filled_params = objects.FillDict(c_nicparams, nic.nicparams)
759 mode = filled_params[constants.NIC_MODE]
760 link = filled_params[constants.NIC_LINK]
761 hooks_nics.append((ip, mac, mode, link))
765 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
766 """Builds instance related env variables for hooks from an object.
768 @type lu: L{LogicalUnit}
769 @param lu: the logical unit on whose behalf we execute
770 @type instance: L{objects.Instance}
771 @param instance: the instance for which we should build the
774 @param override: dictionary with key/values that will override
777 @return: the hook environment dictionary
780 cluster = lu.cfg.GetClusterInfo()
781 bep = cluster.FillBE(instance)
782 hvp = cluster.FillHV(instance)
784 'name': instance.name,
785 'primary_node': instance.primary_node,
786 'secondary_nodes': instance.secondary_nodes,
787 'os_type': instance.os,
788 'status': instance.admin_up,
789 'memory': bep[constants.BE_MEMORY],
790 'vcpus': bep[constants.BE_VCPUS],
791 'nics': _NICListToTuple(lu, instance.nics),
792 'disk_template': instance.disk_template,
793 'disks': [(disk.size, disk.mode) for disk in instance.disks],
796 'hypervisor_name': instance.hypervisor,
799 args.update(override)
800 return _BuildInstanceHookEnv(**args) # pylint: disable-msg=W0142
803 def _AdjustCandidatePool(lu, exceptions):
804 """Adjust the candidate pool after node operations.
807 mod_list = lu.cfg.MaintainCandidatePool(exceptions)
809 lu.LogInfo("Promoted nodes to master candidate role: %s",
810 utils.CommaJoin(node.name for node in mod_list))
811 for name in mod_list:
812 lu.context.ReaddNode(name)
813 mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
815 lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
819 def _DecideSelfPromotion(lu, exceptions=None):
820 """Decide whether I should promote myself as a master candidate.
823 cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
824 mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
825 # the new node will increase mc_max with one, so:
826 mc_should = min(mc_should + 1, cp_size)
827 return mc_now < mc_should
830 def _CheckNicsBridgesExist(lu, target_nics, target_node,
831 profile=constants.PP_DEFAULT):
832 """Check that the brigdes needed by a list of nics exist.
835 c_nicparams = lu.cfg.GetClusterInfo().nicparams[profile]
836 paramslist = [objects.FillDict(c_nicparams, nic.nicparams)
837 for nic in target_nics]
838 brlist = [params[constants.NIC_LINK] for params in paramslist
839 if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
841 result = lu.rpc.call_bridges_exist(target_node, brlist)
842 result.Raise("Error checking bridges on destination node '%s'" %
843 target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
846 def _CheckInstanceBridgesExist(lu, instance, node=None):
847 """Check that the brigdes needed by an instance exist.
851 node = instance.primary_node
852 _CheckNicsBridgesExist(lu, instance.nics, node)
855 def _CheckOSVariant(os_obj, name):
856 """Check whether an OS name conforms to the os variants specification.
858 @type os_obj: L{objects.OS}
859 @param os_obj: OS object to check
861 @param name: OS name passed by the user, to check for validity
864 if not os_obj.supported_variants:
867 variant = name.split("+", 1)[1]
869 raise errors.OpPrereqError("OS name must include a variant",
872 if variant not in os_obj.supported_variants:
873 raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
876 def _GetNodeInstancesInner(cfg, fn):
877 return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
880 def _GetNodeInstances(cfg, node_name):
881 """Returns a list of all primary and secondary instances on a node.
885 return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
888 def _GetNodePrimaryInstances(cfg, node_name):
889 """Returns primary instances on a node.
892 return _GetNodeInstancesInner(cfg,
893 lambda inst: node_name == inst.primary_node)
896 def _GetNodeSecondaryInstances(cfg, node_name):
897 """Returns secondary instances on a node.
900 return _GetNodeInstancesInner(cfg,
901 lambda inst: node_name in inst.secondary_nodes)
904 def _GetStorageTypeArgs(cfg, storage_type):
905 """Returns the arguments for a storage type.
908 # Special case for file storage
909 if storage_type == constants.ST_FILE:
910 # storage.FileStorage wants a list of storage directories
911 return [[cfg.GetFileStorageDir()]]
916 def _FindFaultyInstanceDisks(cfg, rpc, instance, node_name, prereq):
919 for dev in instance.disks:
920 cfg.SetDiskID(dev, node_name)
922 result = rpc.call_blockdev_getmirrorstatus(node_name, instance.disks)
923 result.Raise("Failed to get disk status from node %s" % node_name,
924 prereq=prereq, ecode=errors.ECODE_ENVIRON)
926 for idx, bdev_status in enumerate(result.payload):
927 if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
933 class LUPostInitCluster(LogicalUnit):
934 """Logical unit for running hooks after cluster initialization.
937 HPATH = "cluster-init"
938 HTYPE = constants.HTYPE_CLUSTER
941 def BuildHooksEnv(self):
945 env = {"OP_TARGET": self.cfg.GetClusterName()}
946 mn = self.cfg.GetMasterNode()
949 def CheckPrereq(self):
950 """No prerequisites to check.
955 def Exec(self, feedback_fn):
962 class LUDestroyCluster(LogicalUnit):
963 """Logical unit for destroying the cluster.
966 HPATH = "cluster-destroy"
967 HTYPE = constants.HTYPE_CLUSTER
970 def BuildHooksEnv(self):
974 env = {"OP_TARGET": self.cfg.GetClusterName()}
977 def CheckPrereq(self):
978 """Check prerequisites.
980 This checks whether the cluster is empty.
982 Any errors are signaled by raising errors.OpPrereqError.
985 master = self.cfg.GetMasterNode()
987 nodelist = self.cfg.GetNodeList()
988 if len(nodelist) != 1 or nodelist[0] != master:
989 raise errors.OpPrereqError("There are still %d node(s) in"
990 " this cluster." % (len(nodelist) - 1),
992 instancelist = self.cfg.GetInstanceList()
994 raise errors.OpPrereqError("There are still %d instance(s) in"
995 " this cluster." % len(instancelist),
998 def Exec(self, feedback_fn):
999 """Destroys the cluster.
1002 master = self.cfg.GetMasterNode()
1003 modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
1005 # Run post hooks on master node before it's removed
1006 hm = self.proc.hmclass(self.rpc.call_hooks_runner, self)
1008 hm.RunPhase(constants.HOOKS_PHASE_POST, [master])
1010 # pylint: disable-msg=W0702
1011 self.LogWarning("Errors occurred running hooks on %s" % master)
1013 result = self.rpc.call_node_stop_master(master, False)
1014 result.Raise("Could not disable the master role")
1016 if modify_ssh_setup:
1017 priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
1018 utils.CreateBackup(priv_key)
1019 utils.CreateBackup(pub_key)
1024 def _VerifyCertificate(filename):
1025 """Verifies a certificate for LUVerifyCluster.
1027 @type filename: string
1028 @param filename: Path to PEM file
1032 cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1033 utils.ReadFile(filename))
1034 except Exception, err: # pylint: disable-msg=W0703
1035 return (LUVerifyCluster.ETYPE_ERROR,
1036 "Failed to load X509 certificate %s: %s" % (filename, err))
1039 utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1040 constants.SSL_CERT_EXPIRATION_ERROR)
1043 fnamemsg = "While verifying %s: %s" % (filename, msg)
1048 return (None, fnamemsg)
1049 elif errcode == utils.CERT_WARNING:
1050 return (LUVerifyCluster.ETYPE_WARNING, fnamemsg)
1051 elif errcode == utils.CERT_ERROR:
1052 return (LUVerifyCluster.ETYPE_ERROR, fnamemsg)
1054 raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1057 class LUVerifyCluster(LogicalUnit):
1058 """Verifies the cluster status.
1061 HPATH = "cluster-verify"
1062 HTYPE = constants.HTYPE_CLUSTER
1063 _OP_REQP = ["skip_checks", "verbose", "error_codes", "debug_simulate_errors"]
1066 TCLUSTER = "cluster"
1068 TINSTANCE = "instance"
1070 ECLUSTERCFG = (TCLUSTER, "ECLUSTERCFG")
1071 ECLUSTERCERT = (TCLUSTER, "ECLUSTERCERT")
1072 EINSTANCEBADNODE = (TINSTANCE, "EINSTANCEBADNODE")
1073 EINSTANCEDOWN = (TINSTANCE, "EINSTANCEDOWN")
1074 EINSTANCELAYOUT = (TINSTANCE, "EINSTANCELAYOUT")
1075 EINSTANCEMISSINGDISK = (TINSTANCE, "EINSTANCEMISSINGDISK")
1076 EINSTANCEMISSINGDISK = (TINSTANCE, "EINSTANCEMISSINGDISK")
1077 EINSTANCEWRONGNODE = (TINSTANCE, "EINSTANCEWRONGNODE")
1078 ENODEDRBD = (TNODE, "ENODEDRBD")
1079 ENODEFILECHECK = (TNODE, "ENODEFILECHECK")
1080 ENODEHOOKS = (TNODE, "ENODEHOOKS")
1081 ENODEHV = (TNODE, "ENODEHV")
1082 ENODELVM = (TNODE, "ENODELVM")
1083 ENODEN1 = (TNODE, "ENODEN1")
1084 ENODENET = (TNODE, "ENODENET")
1085 ENODEORPHANINSTANCE = (TNODE, "ENODEORPHANINSTANCE")
1086 ENODEORPHANLV = (TNODE, "ENODEORPHANLV")
1087 ENODERPC = (TNODE, "ENODERPC")
1088 ENODESSH = (TNODE, "ENODESSH")
1089 ENODEVERSION = (TNODE, "ENODEVERSION")
1090 ENODESETUP = (TNODE, "ENODESETUP")
1091 ENODETIME = (TNODE, "ENODETIME")
1093 ETYPE_FIELD = "code"
1094 ETYPE_ERROR = "ERROR"
1095 ETYPE_WARNING = "WARNING"
1097 class NodeImage(object):
1098 """A class representing the logical and physical status of a node.
1100 @ivar volumes: a structure as returned from
1101 L{ganeti.backend.GetVolumeList} (runtime)
1102 @ivar instances: a list of running instances (runtime)
1103 @ivar pinst: list of configured primary instances (config)
1104 @ivar sinst: list of configured secondary instances (config)
1105 @ivar sbp: diction of {secondary-node: list of instances} of all peers
1106 of this node (config)
1107 @ivar mfree: free memory, as reported by hypervisor (runtime)
1108 @ivar dfree: free disk, as reported by the node (runtime)
1109 @ivar offline: the offline status (config)
1110 @type rpc_fail: boolean
1111 @ivar rpc_fail: whether the RPC verify call was successfull (overall,
1112 not whether the individual keys were correct) (runtime)
1113 @type lvm_fail: boolean
1114 @ivar lvm_fail: whether the RPC call didn't return valid LVM data
1115 @type hyp_fail: boolean
1116 @ivar hyp_fail: whether the RPC call didn't return the instance list
1117 @type ghost: boolean
1118 @ivar ghost: whether this is a known node or not (config)
1121 def __init__(self, offline=False):
1129 self.offline = offline
1130 self.rpc_fail = False
1131 self.lvm_fail = False
1132 self.hyp_fail = False
1135 def ExpandNames(self):
1136 self.needed_locks = {
1137 locking.LEVEL_NODE: locking.ALL_SET,
1138 locking.LEVEL_INSTANCE: locking.ALL_SET,
1140 self.share_locks = dict.fromkeys(locking.LEVELS, 1)
1142 def _Error(self, ecode, item, msg, *args, **kwargs):
1143 """Format an error message.
1145 Based on the opcode's error_codes parameter, either format a
1146 parseable error code, or a simpler error string.
1148 This must be called only from Exec and functions called from Exec.
1151 ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1153 # first complete the msg
1156 # then format the whole message
1157 if self.op.error_codes:
1158 msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1164 msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1165 # and finally report it via the feedback_fn
1166 self._feedback_fn(" - %s" % msg)
1168 def _ErrorIf(self, cond, *args, **kwargs):
1169 """Log an error message if the passed condition is True.
1172 cond = bool(cond) or self.op.debug_simulate_errors
1174 self._Error(*args, **kwargs)
1175 # do not mark the operation as failed for WARN cases only
1176 if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
1177 self.bad = self.bad or cond
1179 def _VerifyNode(self, ninfo, nresult):
1180 """Run multiple tests against a node.
1184 - compares ganeti version
1185 - checks vg existence and size > 20G
1186 - checks config file checksum
1187 - checks ssh to other nodes
1189 @type ninfo: L{objects.Node}
1190 @param ninfo: the node to check
1191 @param nresult: the results from the node
1193 @return: whether overall this call was successful (and we can expect
1194 reasonable values in the respose)
1198 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1200 # main result, nresult should be a non-empty dict
1201 test = not nresult or not isinstance(nresult, dict)
1202 _ErrorIf(test, self.ENODERPC, node,
1203 "unable to verify node: no data returned")
1207 # compares ganeti version
1208 local_version = constants.PROTOCOL_VERSION
1209 remote_version = nresult.get("version", None)
1210 test = not (remote_version and
1211 isinstance(remote_version, (list, tuple)) and
1212 len(remote_version) == 2)
1213 _ErrorIf(test, self.ENODERPC, node,
1214 "connection to node returned invalid data")
1218 test = local_version != remote_version[0]
1219 _ErrorIf(test, self.ENODEVERSION, node,
1220 "incompatible protocol versions: master %s,"
1221 " node %s", local_version, remote_version[0])
1225 # node seems compatible, we can actually try to look into its results
1227 # full package version
1228 self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
1229 self.ENODEVERSION, node,
1230 "software version mismatch: master %s, node %s",
1231 constants.RELEASE_VERSION, remote_version[1],
1232 code=self.ETYPE_WARNING)
1234 hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
1235 if isinstance(hyp_result, dict):
1236 for hv_name, hv_result in hyp_result.iteritems():
1237 test = hv_result is not None
1238 _ErrorIf(test, self.ENODEHV, node,
1239 "hypervisor %s verify failure: '%s'", hv_name, hv_result)
1242 test = nresult.get(constants.NV_NODESETUP,
1243 ["Missing NODESETUP results"])
1244 _ErrorIf(test, self.ENODESETUP, node, "node setup error: %s",
1249 def _VerifyNodeTime(self, ninfo, nresult,
1250 nvinfo_starttime, nvinfo_endtime):
1251 """Check the node time.
1253 @type ninfo: L{objects.Node}
1254 @param ninfo: the node to check
1255 @param nresult: the remote results for the node
1256 @param nvinfo_starttime: the start time of the RPC call
1257 @param nvinfo_endtime: the end time of the RPC call
1261 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1263 ntime = nresult.get(constants.NV_TIME, None)
1265 ntime_merged = utils.MergeTime(ntime)
1266 except (ValueError, TypeError):
1267 _ErrorIf(True, self.ENODETIME, node, "Node returned invalid time")
1270 if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
1271 ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
1272 elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
1273 ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
1277 _ErrorIf(ntime_diff is not None, self.ENODETIME, node,
1278 "Node time diverges by at least %s from master node time",
1281 def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
1282 """Check the node time.
1284 @type ninfo: L{objects.Node}
1285 @param ninfo: the node to check
1286 @param nresult: the remote results for the node
1287 @param vg_name: the configured VG name
1294 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1296 # checks vg existence and size > 20G
1297 vglist = nresult.get(constants.NV_VGLIST, None)
1299 _ErrorIf(test, self.ENODELVM, node, "unable to check volume groups")
1301 vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
1302 constants.MIN_VG_SIZE)
1303 _ErrorIf(vgstatus, self.ENODELVM, node, vgstatus)
1306 pvlist = nresult.get(constants.NV_PVLIST, None)
1307 test = pvlist is None
1308 _ErrorIf(test, self.ENODELVM, node, "Can't get PV list from node")
1310 # check that ':' is not present in PV names, since it's a
1311 # special character for lvcreate (denotes the range of PEs to
1313 for _, pvname, owner_vg in pvlist:
1314 test = ":" in pvname
1315 _ErrorIf(test, self.ENODELVM, node, "Invalid character ':' in PV"
1316 " '%s' of VG '%s'", pvname, owner_vg)
1318 def _VerifyNodeNetwork(self, ninfo, nresult):
1319 """Check the node time.
1321 @type ninfo: L{objects.Node}
1322 @param ninfo: the node to check
1323 @param nresult: the remote results for the node
1327 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1329 test = constants.NV_NODELIST not in nresult
1330 _ErrorIf(test, self.ENODESSH, node,
1331 "node hasn't returned node ssh connectivity data")
1333 if nresult[constants.NV_NODELIST]:
1334 for a_node, a_msg in nresult[constants.NV_NODELIST].items():
1335 _ErrorIf(True, self.ENODESSH, node,
1336 "ssh communication with node '%s': %s", a_node, a_msg)
1338 test = constants.NV_NODENETTEST not in nresult
1339 _ErrorIf(test, self.ENODENET, node,
1340 "node hasn't returned node tcp connectivity data")
1342 if nresult[constants.NV_NODENETTEST]:
1343 nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
1345 _ErrorIf(True, self.ENODENET, node,
1346 "tcp communication with node '%s': %s",
1347 anode, nresult[constants.NV_NODENETTEST][anode])
1349 test = constants.NV_MASTERIP not in nresult
1350 _ErrorIf(test, self.ENODENET, node,
1351 "node hasn't returned node master IP reachability data")
1353 if not nresult[constants.NV_MASTERIP]:
1354 if node == self.master_node:
1355 msg = "the master node cannot reach the master IP (not configured?)"
1357 msg = "cannot reach the master IP"
1358 _ErrorIf(True, self.ENODENET, node, msg)
1361 def _VerifyInstance(self, instance, instanceconfig, node_image):
1362 """Verify an instance.
1364 This function checks to see if the required block devices are
1365 available on the instance's node.
1368 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1369 node_current = instanceconfig.primary_node
1371 node_vol_should = {}
1372 instanceconfig.MapLVsByNode(node_vol_should)
1374 for node in node_vol_should:
1375 n_img = node_image[node]
1376 if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
1377 # ignore missing volumes on offline or broken nodes
1379 for volume in node_vol_should[node]:
1380 test = volume not in n_img.volumes
1381 _ErrorIf(test, self.EINSTANCEMISSINGDISK, instance,
1382 "volume %s missing on node %s", volume, node)
1384 if instanceconfig.admin_up:
1385 pri_img = node_image[node_current]
1386 test = instance not in pri_img.instances and not pri_img.offline
1387 _ErrorIf(test, self.EINSTANCEDOWN, instance,
1388 "instance not running on its primary node %s",
1391 for node, n_img in node_image.items():
1392 if (not node == node_current):
1393 test = instance in n_img.instances
1394 _ErrorIf(test, self.EINSTANCEWRONGNODE, instance,
1395 "instance should not run on node %s", node)
1397 def _VerifyOrphanVolumes(self, node_vol_should, node_image):
1398 """Verify if there are any unknown volumes in the cluster.
1400 The .os, .swap and backup volumes are ignored. All other volumes are
1401 reported as unknown.
1404 for node, n_img in node_image.items():
1405 if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
1406 # skip non-healthy nodes
1408 for volume in n_img.volumes:
1409 test = (node not in node_vol_should or
1410 volume not in node_vol_should[node])
1411 self._ErrorIf(test, self.ENODEORPHANLV, node,
1412 "volume %s is unknown", volume)
1414 def _VerifyOrphanInstances(self, instancelist, node_image):
1415 """Verify the list of running instances.
1417 This checks what instances are running but unknown to the cluster.
1420 for node, n_img in node_image.items():
1421 for o_inst in n_img.instances:
1422 test = o_inst not in instancelist
1423 self._ErrorIf(test, self.ENODEORPHANINSTANCE, node,
1424 "instance %s on node %s should not exist", o_inst, node)
1426 def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
1427 """Verify N+1 Memory Resilience.
1429 Check that if one single node dies we can still start all the
1430 instances it was primary for.
1433 for node, n_img in node_image.items():
1434 # This code checks that every node which is now listed as
1435 # secondary has enough memory to host all instances it is
1436 # supposed to should a single other node in the cluster fail.
1437 # FIXME: not ready for failover to an arbitrary node
1438 # FIXME: does not support file-backed instances
1439 # WARNING: we currently take into account down instances as well
1440 # as up ones, considering that even if they're down someone
1441 # might want to start them even in the event of a node failure.
1442 for prinode, instances in n_img.sbp.items():
1444 for instance in instances:
1445 bep = self.cfg.GetClusterInfo().FillBE(instance_cfg[instance])
1446 if bep[constants.BE_AUTO_BALANCE]:
1447 needed_mem += bep[constants.BE_MEMORY]
1448 test = n_img.mfree < needed_mem
1449 self._ErrorIf(test, self.ENODEN1, node,
1450 "not enough memory on to accommodate"
1451 " failovers should peer node %s fail", prinode)
1453 def _VerifyNodeFiles(self, ninfo, nresult, file_list, local_cksum,
1455 """Verifies and computes the node required file checksums.
1457 @type ninfo: L{objects.Node}
1458 @param ninfo: the node to check
1459 @param nresult: the remote results for the node
1460 @param file_list: required list of files
1461 @param local_cksum: dictionary of local files and their checksums
1462 @param master_files: list of files that only masters should have
1466 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1468 remote_cksum = nresult.get(constants.NV_FILELIST, None)
1469 test = not isinstance(remote_cksum, dict)
1470 _ErrorIf(test, self.ENODEFILECHECK, node,
1471 "node hasn't returned file checksum data")
1475 for file_name in file_list:
1476 node_is_mc = ninfo.master_candidate
1477 must_have = (file_name not in master_files) or node_is_mc
1479 test1 = file_name not in remote_cksum
1481 test2 = not test1 and remote_cksum[file_name] != local_cksum[file_name]
1483 test3 = not test1 and remote_cksum[file_name] == local_cksum[file_name]
1484 _ErrorIf(test1 and must_have, self.ENODEFILECHECK, node,
1485 "file '%s' missing", file_name)
1486 _ErrorIf(test2 and must_have, self.ENODEFILECHECK, node,
1487 "file '%s' has wrong checksum", file_name)
1488 # not candidate and this is not a must-have file
1489 _ErrorIf(test2 and not must_have, self.ENODEFILECHECK, node,
1490 "file '%s' should not exist on non master"
1491 " candidates (and the file is outdated)", file_name)
1492 # all good, except non-master/non-must have combination
1493 _ErrorIf(test3 and not must_have, self.ENODEFILECHECK, node,
1494 "file '%s' should not exist"
1495 " on non master candidates", file_name)
1497 def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_map):
1498 """Verifies and the node DRBD status.
1500 @type ninfo: L{objects.Node}
1501 @param ninfo: the node to check
1502 @param nresult: the remote results for the node
1503 @param instanceinfo: the dict of instances
1504 @param drbd_map: the DRBD map as returned by
1505 L{ganeti.config.ConfigWriter.ComputeDRBDMap}
1509 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1511 # compute the DRBD minors
1513 for minor, instance in drbd_map[node].items():
1514 test = instance not in instanceinfo
1515 _ErrorIf(test, self.ECLUSTERCFG, None,
1516 "ghost instance '%s' in temporary DRBD map", instance)
1517 # ghost instance should not be running, but otherwise we
1518 # don't give double warnings (both ghost instance and
1519 # unallocated minor in use)
1521 node_drbd[minor] = (instance, False)
1523 instance = instanceinfo[instance]
1524 node_drbd[minor] = (instance.name, instance.admin_up)
1526 # and now check them
1527 used_minors = nresult.get(constants.NV_DRBDLIST, [])
1528 test = not isinstance(used_minors, (tuple, list))
1529 _ErrorIf(test, self.ENODEDRBD, node,
1530 "cannot parse drbd status file: %s", str(used_minors))
1532 # we cannot check drbd status
1535 for minor, (iname, must_exist) in node_drbd.items():
1536 test = minor not in used_minors and must_exist
1537 _ErrorIf(test, self.ENODEDRBD, node,
1538 "drbd minor %d of instance %s is not active", minor, iname)
1539 for minor in used_minors:
1540 test = minor not in node_drbd
1541 _ErrorIf(test, self.ENODEDRBD, node,
1542 "unallocated drbd minor %d is in use", minor)
1544 def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
1545 """Verifies and updates the node volume data.
1547 This function will update a L{NodeImage}'s internal structures
1548 with data from the remote call.
1550 @type ninfo: L{objects.Node}
1551 @param ninfo: the node to check
1552 @param nresult: the remote results for the node
1553 @param nimg: the node image object
1554 @param vg_name: the configured VG name
1558 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1560 nimg.lvm_fail = True
1561 lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
1564 elif isinstance(lvdata, basestring):
1565 _ErrorIf(True, self.ENODELVM, node, "LVM problem on node: %s",
1566 utils.SafeEncode(lvdata))
1567 elif not isinstance(lvdata, dict):
1568 _ErrorIf(True, self.ENODELVM, node, "rpc call to node failed (lvlist)")
1570 nimg.volumes = lvdata
1571 nimg.lvm_fail = False
1573 def _UpdateNodeInstances(self, ninfo, nresult, nimg):
1574 """Verifies and updates the node instance list.
1576 If the listing was successful, then updates this node's instance
1577 list. Otherwise, it marks the RPC call as failed for the instance
1580 @type ninfo: L{objects.Node}
1581 @param ninfo: the node to check
1582 @param nresult: the remote results for the node
1583 @param nimg: the node image object
1586 idata = nresult.get(constants.NV_INSTANCELIST, None)
1587 test = not isinstance(idata, list)
1588 self._ErrorIf(test, self.ENODEHV, ninfo.name, "rpc call to node failed"
1589 " (instancelist): %s", utils.SafeEncode(str(idata)))
1591 nimg.hyp_fail = True
1593 nimg.instances = idata
1595 def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
1596 """Verifies and computes a node information map
1598 @type ninfo: L{objects.Node}
1599 @param ninfo: the node to check
1600 @param nresult: the remote results for the node
1601 @param nimg: the node image object
1602 @param vg_name: the configured VG name
1606 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1608 # try to read free memory (from the hypervisor)
1609 hv_info = nresult.get(constants.NV_HVINFO, None)
1610 test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
1611 _ErrorIf(test, self.ENODEHV, node, "rpc call to node failed (hvinfo)")
1614 nimg.mfree = int(hv_info["memory_free"])
1615 except (ValueError, TypeError):
1616 _ErrorIf(True, self.ENODERPC, node,
1617 "node returned invalid nodeinfo, check hypervisor")
1619 # FIXME: devise a free space model for file based instances as well
1620 if vg_name is not None:
1621 test = (constants.NV_VGLIST not in nresult or
1622 vg_name not in nresult[constants.NV_VGLIST])
1623 _ErrorIf(test, self.ENODELVM, node,
1624 "node didn't return data for the volume group '%s'"
1625 " - it is either missing or broken", vg_name)
1628 nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
1629 except (ValueError, TypeError):
1630 _ErrorIf(True, self.ENODERPC, node,
1631 "node returned invalid LVM info, check LVM status")
1633 def CheckPrereq(self):
1634 """Check prerequisites.
1636 Transform the list of checks we're going to skip into a set and check that
1637 all its members are valid.
1640 self.skip_set = frozenset(self.op.skip_checks)
1641 if not constants.VERIFY_OPTIONAL_CHECKS.issuperset(self.skip_set):
1642 raise errors.OpPrereqError("Invalid checks to be skipped specified",
1645 def BuildHooksEnv(self):
1648 Cluster-Verify hooks just ran in the post phase and their failure makes
1649 the output be logged in the verify output and the verification to fail.
1652 all_nodes = self.cfg.GetNodeList()
1654 "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
1656 for node in self.cfg.GetAllNodesInfo().values():
1657 env["NODE_TAGS_%s" % node.name] = " ".join(node.GetTags())
1659 return env, [], all_nodes
1661 def Exec(self, feedback_fn):
1662 """Verify integrity of cluster, performing various test on nodes.
1666 _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1667 verbose = self.op.verbose
1668 self._feedback_fn = feedback_fn
1669 feedback_fn("* Verifying global settings")
1670 for msg in self.cfg.VerifyConfig():
1671 _ErrorIf(True, self.ECLUSTERCFG, None, msg)
1673 # Check the cluster certificates
1674 for cert_filename in constants.ALL_CERT_FILES:
1675 (errcode, msg) = _VerifyCertificate(cert_filename)
1676 _ErrorIf(errcode, self.ECLUSTERCERT, None, msg, code=errcode)
1678 vg_name = self.cfg.GetVGName()
1679 hypervisors = self.cfg.GetClusterInfo().enabled_hypervisors
1680 cluster = self.cfg.GetClusterInfo()
1681 nodelist = utils.NiceSort(self.cfg.GetNodeList())
1682 nodeinfo = [self.cfg.GetNodeInfo(nname) for nname in nodelist]
1683 instancelist = utils.NiceSort(self.cfg.GetInstanceList())
1684 instanceinfo = dict((iname, self.cfg.GetInstanceInfo(iname))
1685 for iname in instancelist)
1686 i_non_redundant = [] # Non redundant instances
1687 i_non_a_balanced = [] # Non auto-balanced instances
1688 n_offline = 0 # Count of offline nodes
1689 n_drained = 0 # Count of nodes being drained
1690 node_vol_should = {}
1692 # FIXME: verify OS list
1693 # do local checksums
1694 master_files = [constants.CLUSTER_CONF_FILE]
1695 master_node = self.master_node = self.cfg.GetMasterNode()
1696 master_ip = self.cfg.GetMasterIP()
1698 file_names = ssconf.SimpleStore().GetFileList()
1699 file_names.extend(constants.ALL_CERT_FILES)
1700 file_names.extend(master_files)
1701 if cluster.modify_etc_hosts:
1702 file_names.append(constants.ETC_HOSTS)
1704 local_checksums = utils.FingerprintFiles(file_names)
1706 feedback_fn("* Gathering data (%d nodes)" % len(nodelist))
1707 node_verify_param = {
1708 constants.NV_FILELIST: file_names,
1709 constants.NV_NODELIST: [node.name for node in nodeinfo
1710 if not node.offline],
1711 constants.NV_HYPERVISOR: hypervisors,
1712 constants.NV_NODENETTEST: [(node.name, node.primary_ip,
1713 node.secondary_ip) for node in nodeinfo
1714 if not node.offline],
1715 constants.NV_INSTANCELIST: hypervisors,
1716 constants.NV_VERSION: None,
1717 constants.NV_HVINFO: self.cfg.GetHypervisorType(),
1718 constants.NV_NODESETUP: None,
1719 constants.NV_TIME: None,
1720 constants.NV_MASTERIP: (master_node, master_ip),
1723 if vg_name is not None:
1724 node_verify_param[constants.NV_VGLIST] = None
1725 node_verify_param[constants.NV_LVLIST] = vg_name
1726 node_verify_param[constants.NV_PVLIST] = [vg_name]
1727 node_verify_param[constants.NV_DRBDLIST] = None
1729 # Build our expected cluster state
1730 node_image = dict((node.name, self.NodeImage(offline=node.offline))
1731 for node in nodeinfo)
1733 for instance in instancelist:
1734 inst_config = instanceinfo[instance]
1736 for nname in inst_config.all_nodes:
1737 if nname not in node_image:
1739 gnode = self.NodeImage()
1741 node_image[nname] = gnode
1743 inst_config.MapLVsByNode(node_vol_should)
1745 pnode = inst_config.primary_node
1746 node_image[pnode].pinst.append(instance)
1748 for snode in inst_config.secondary_nodes:
1749 nimg = node_image[snode]
1750 nimg.sinst.append(instance)
1751 if pnode not in nimg.sbp:
1752 nimg.sbp[pnode] = []
1753 nimg.sbp[pnode].append(instance)
1755 # At this point, we have the in-memory data structures complete,
1756 # except for the runtime information, which we'll gather next
1758 # Due to the way our RPC system works, exact response times cannot be
1759 # guaranteed (e.g. a broken node could run into a timeout). By keeping the
1760 # time before and after executing the request, we can at least have a time
1762 nvinfo_starttime = time.time()
1763 all_nvinfo = self.rpc.call_node_verify(nodelist, node_verify_param,
1764 self.cfg.GetClusterName())
1765 nvinfo_endtime = time.time()
1767 all_drbd_map = self.cfg.ComputeDRBDMap()
1769 feedback_fn("* Verifying node status")
1770 for node_i in nodeinfo:
1772 nimg = node_image[node]
1776 feedback_fn("* Skipping offline node %s" % (node,))
1780 if node == master_node:
1782 elif node_i.master_candidate:
1783 ntype = "master candidate"
1784 elif node_i.drained:
1790 feedback_fn("* Verifying node %s (%s)" % (node, ntype))
1792 msg = all_nvinfo[node].fail_msg
1793 _ErrorIf(msg, self.ENODERPC, node, "while contacting node: %s", msg)
1795 nimg.rpc_fail = True
1798 nresult = all_nvinfo[node].payload
1800 nimg.call_ok = self._VerifyNode(node_i, nresult)
1801 self._VerifyNodeNetwork(node_i, nresult)
1802 self._VerifyNodeLVM(node_i, nresult, vg_name)
1803 self._VerifyNodeFiles(node_i, nresult, file_names, local_checksums,
1805 self._VerifyNodeDrbd(node_i, nresult, instanceinfo, all_drbd_map)
1806 self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
1808 self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
1809 self._UpdateNodeInstances(node_i, nresult, nimg)
1810 self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
1812 feedback_fn("* Verifying instance status")
1813 for instance in instancelist:
1815 feedback_fn("* Verifying instance %s" % instance)
1816 inst_config = instanceinfo[instance]
1817 self._VerifyInstance(instance, inst_config, node_image)
1818 inst_nodes_offline = []
1820 pnode = inst_config.primary_node
1821 pnode_img = node_image[pnode]
1822 _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
1823 self.ENODERPC, pnode, "instance %s, connection to"
1824 " primary node failed", instance)
1826 if pnode_img.offline:
1827 inst_nodes_offline.append(pnode)
1829 # If the instance is non-redundant we cannot survive losing its primary
1830 # node, so we are not N+1 compliant. On the other hand we have no disk
1831 # templates with more than one secondary so that situation is not well
1833 # FIXME: does not support file-backed instances
1834 if not inst_config.secondary_nodes:
1835 i_non_redundant.append(instance)
1836 _ErrorIf(len(inst_config.secondary_nodes) > 1, self.EINSTANCELAYOUT,
1837 instance, "instance has multiple secondary nodes: %s",
1838 utils.CommaJoin(inst_config.secondary_nodes),
1839 code=self.ETYPE_WARNING)
1841 if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
1842 i_non_a_balanced.append(instance)
1844 for snode in inst_config.secondary_nodes:
1845 s_img = node_image[snode]
1846 _ErrorIf(s_img.rpc_fail and not s_img.offline, self.ENODERPC, snode,
1847 "instance %s, connection to secondary node failed", instance)
1850 inst_nodes_offline.append(snode)
1852 # warn that the instance lives on offline nodes
1853 _ErrorIf(inst_nodes_offline, self.EINSTANCEBADNODE, instance,
1854 "instance lives on offline node(s) %s",
1855 utils.CommaJoin(inst_nodes_offline))
1856 # ... or ghost nodes
1857 for node in inst_config.all_nodes:
1858 _ErrorIf(node_image[node].ghost, self.EINSTANCEBADNODE, instance,
1859 "instance lives on ghost node %s", node)
1861 feedback_fn("* Verifying orphan volumes")
1862 self._VerifyOrphanVolumes(node_vol_should, node_image)
1864 feedback_fn("* Verifying orphan instances")
1865 self._VerifyOrphanInstances(instancelist, node_image)
1867 if constants.VERIFY_NPLUSONE_MEM not in self.skip_set:
1868 feedback_fn("* Verifying N+1 Memory redundancy")
1869 self._VerifyNPlusOneMemory(node_image, instanceinfo)
1871 feedback_fn("* Other Notes")
1873 feedback_fn(" - NOTICE: %d non-redundant instance(s) found."
1874 % len(i_non_redundant))
1876 if i_non_a_balanced:
1877 feedback_fn(" - NOTICE: %d non-auto-balanced instance(s) found."
1878 % len(i_non_a_balanced))
1881 feedback_fn(" - NOTICE: %d offline node(s) found." % n_offline)
1884 feedback_fn(" - NOTICE: %d drained node(s) found." % n_drained)
1888 def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
1889 """Analyze the post-hooks' result
1891 This method analyses the hook result, handles it, and sends some
1892 nicely-formatted feedback back to the user.
1894 @param phase: one of L{constants.HOOKS_PHASE_POST} or
1895 L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
1896 @param hooks_results: the results of the multi-node hooks rpc call
1897 @param feedback_fn: function used send feedback back to the caller
1898 @param lu_result: previous Exec result
1899 @return: the new Exec result, based on the previous result
1903 # We only really run POST phase hooks, and are only interested in
1905 if phase == constants.HOOKS_PHASE_POST:
1906 # Used to change hooks' output to proper indentation
1907 indent_re = re.compile('^', re.M)
1908 feedback_fn("* Hooks Results")
1909 assert hooks_results, "invalid result from hooks"
1911 for node_name in hooks_results:
1912 res = hooks_results[node_name]
1914 test = msg and not res.offline
1915 self._ErrorIf(test, self.ENODEHOOKS, node_name,
1916 "Communication failure in hooks execution: %s", msg)
1917 if res.offline or msg:
1918 # No need to investigate payload if node is offline or gave an error.
1919 # override manually lu_result here as _ErrorIf only
1920 # overrides self.bad
1923 for script, hkr, output in res.payload:
1924 test = hkr == constants.HKR_FAIL
1925 self._ErrorIf(test, self.ENODEHOOKS, node_name,
1926 "Script %s failed, output:", script)
1928 output = indent_re.sub(' ', output)
1929 feedback_fn("%s" % output)
1935 class LUVerifyDisks(NoHooksLU):
1936 """Verifies the cluster disks status.
1942 def ExpandNames(self):
1943 self.needed_locks = {
1944 locking.LEVEL_NODE: locking.ALL_SET,
1945 locking.LEVEL_INSTANCE: locking.ALL_SET,
1947 self.share_locks = dict.fromkeys(locking.LEVELS, 1)
1949 def CheckPrereq(self):
1950 """Check prerequisites.
1952 This has no prerequisites.
1957 def Exec(self, feedback_fn):
1958 """Verify integrity of cluster disks.
1960 @rtype: tuple of three items
1961 @return: a tuple of (dict of node-to-node_error, list of instances
1962 which need activate-disks, dict of instance: (node, volume) for
1966 result = res_nodes, res_instances, res_missing = {}, [], {}
1968 vg_name = self.cfg.GetVGName()
1969 nodes = utils.NiceSort(self.cfg.GetNodeList())
1970 instances = [self.cfg.GetInstanceInfo(name)
1971 for name in self.cfg.GetInstanceList()]
1974 for inst in instances:
1976 if (not inst.admin_up or
1977 inst.disk_template not in constants.DTS_NET_MIRROR):
1979 inst.MapLVsByNode(inst_lvs)
1980 # transform { iname: {node: [vol,],},} to {(node, vol): iname}
1981 for node, vol_list in inst_lvs.iteritems():
1982 for vol in vol_list:
1983 nv_dict[(node, vol)] = inst
1988 node_lvs = self.rpc.call_lv_list(nodes, vg_name)
1992 node_res = node_lvs[node]
1993 if node_res.offline:
1995 msg = node_res.fail_msg
1997 logging.warning("Error enumerating LVs on node %s: %s", node, msg)
1998 res_nodes[node] = msg
2001 lvs = node_res.payload
2002 for lv_name, (_, _, lv_online) in lvs.items():
2003 inst = nv_dict.pop((node, lv_name), None)
2004 if (not lv_online and inst is not None
2005 and inst.name not in res_instances):
2006 res_instances.append(inst.name)
2008 # any leftover items in nv_dict are missing LVs, let's arrange the
2010 for key, inst in nv_dict.iteritems():
2011 if inst.name not in res_missing:
2012 res_missing[inst.name] = []
2013 res_missing[inst.name].append(key)
2018 class LURepairDiskSizes(NoHooksLU):
2019 """Verifies the cluster disks sizes.
2022 _OP_REQP = ["instances"]
2025 def ExpandNames(self):
2026 if not isinstance(self.op.instances, list):
2027 raise errors.OpPrereqError("Invalid argument type 'instances'",
2030 if self.op.instances:
2031 self.wanted_names = []
2032 for name in self.op.instances:
2033 full_name = _ExpandInstanceName(self.cfg, name)
2034 self.wanted_names.append(full_name)
2035 self.needed_locks = {
2036 locking.LEVEL_NODE: [],
2037 locking.LEVEL_INSTANCE: self.wanted_names,
2039 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2041 self.wanted_names = None
2042 self.needed_locks = {
2043 locking.LEVEL_NODE: locking.ALL_SET,
2044 locking.LEVEL_INSTANCE: locking.ALL_SET,
2046 self.share_locks = dict(((i, 1) for i in locking.LEVELS))
2048 def DeclareLocks(self, level):
2049 if level == locking.LEVEL_NODE and self.wanted_names is not None:
2050 self._LockInstancesNodes(primary_only=True)
2052 def CheckPrereq(self):
2053 """Check prerequisites.
2055 This only checks the optional instance list against the existing names.
2058 if self.wanted_names is None:
2059 self.wanted_names = self.acquired_locks[locking.LEVEL_INSTANCE]
2061 self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name
2062 in self.wanted_names]
2064 def _EnsureChildSizes(self, disk):
2065 """Ensure children of the disk have the needed disk size.
2067 This is valid mainly for DRBD8 and fixes an issue where the
2068 children have smaller disk size.
2070 @param disk: an L{ganeti.objects.Disk} object
2073 if disk.dev_type == constants.LD_DRBD8:
2074 assert disk.children, "Empty children for DRBD8?"
2075 fchild = disk.children[0]
2076 mismatch = fchild.size < disk.size
2078 self.LogInfo("Child disk has size %d, parent %d, fixing",
2079 fchild.size, disk.size)
2080 fchild.size = disk.size
2082 # and we recurse on this child only, not on the metadev
2083 return self._EnsureChildSizes(fchild) or mismatch
2087 def Exec(self, feedback_fn):
2088 """Verify the size of cluster disks.
2091 # TODO: check child disks too
2092 # TODO: check differences in size between primary/secondary nodes
2094 for instance in self.wanted_instances:
2095 pnode = instance.primary_node
2096 if pnode not in per_node_disks:
2097 per_node_disks[pnode] = []
2098 for idx, disk in enumerate(instance.disks):
2099 per_node_disks[pnode].append((instance, idx, disk))
2102 for node, dskl in per_node_disks.items():
2103 newl = [v[2].Copy() for v in dskl]
2105 self.cfg.SetDiskID(dsk, node)
2106 result = self.rpc.call_blockdev_getsizes(node, newl)
2108 self.LogWarning("Failure in blockdev_getsizes call to node"
2109 " %s, ignoring", node)
2111 if len(result.data) != len(dskl):
2112 self.LogWarning("Invalid result from node %s, ignoring node results",
2115 for ((instance, idx, disk), size) in zip(dskl, result.data):
2117 self.LogWarning("Disk %d of instance %s did not return size"
2118 " information, ignoring", idx, instance.name)
2120 if not isinstance(size, (int, long)):
2121 self.LogWarning("Disk %d of instance %s did not return valid"
2122 " size information, ignoring", idx, instance.name)
2125 if size != disk.size:
2126 self.LogInfo("Disk %d of instance %s has mismatched size,"
2127 " correcting: recorded %d, actual %d", idx,
2128 instance.name, disk.size, size)
2130 self.cfg.Update(instance, feedback_fn)
2131 changed.append((instance.name, idx, size))
2132 if self._EnsureChildSizes(disk):
2133 self.cfg.Update(instance, feedback_fn)
2134 changed.append((instance.name, idx, disk.size))
2138 class LURenameCluster(LogicalUnit):
2139 """Rename the cluster.
2142 HPATH = "cluster-rename"
2143 HTYPE = constants.HTYPE_CLUSTER
2146 def BuildHooksEnv(self):
2151 "OP_TARGET": self.cfg.GetClusterName(),
2152 "NEW_NAME": self.op.name,
2154 mn = self.cfg.GetMasterNode()
2155 all_nodes = self.cfg.GetNodeList()
2156 return env, [mn], all_nodes
2158 def CheckPrereq(self):
2159 """Verify that the passed name is a valid one.
2162 hostname = utils.GetHostInfo(self.op.name)
2164 new_name = hostname.name
2165 self.ip = new_ip = hostname.ip
2166 old_name = self.cfg.GetClusterName()
2167 old_ip = self.cfg.GetMasterIP()
2168 if new_name == old_name and new_ip == old_ip:
2169 raise errors.OpPrereqError("Neither the name nor the IP address of the"
2170 " cluster has changed",
2172 if new_ip != old_ip:
2173 if utils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
2174 raise errors.OpPrereqError("The given cluster IP address (%s) is"
2175 " reachable on the network. Aborting." %
2176 new_ip, errors.ECODE_NOTUNIQUE)
2178 self.op.name = new_name
2180 def Exec(self, feedback_fn):
2181 """Rename the cluster.
2184 clustername = self.op.name
2187 # shutdown the master IP
2188 master = self.cfg.GetMasterNode()
2189 result = self.rpc.call_node_stop_master(master, False)
2190 result.Raise("Could not disable the master role")
2193 cluster = self.cfg.GetClusterInfo()
2194 cluster.cluster_name = clustername
2195 cluster.master_ip = ip
2196 self.cfg.Update(cluster, feedback_fn)
2198 # update the known hosts file
2199 ssh.WriteKnownHostsFile(self.cfg, constants.SSH_KNOWN_HOSTS_FILE)
2200 node_list = self.cfg.GetNodeList()
2202 node_list.remove(master)
2205 result = self.rpc.call_upload_file(node_list,
2206 constants.SSH_KNOWN_HOSTS_FILE)
2207 for to_node, to_result in result.iteritems():
2208 msg = to_result.fail_msg
2210 msg = ("Copy of file %s to node %s failed: %s" %
2211 (constants.SSH_KNOWN_HOSTS_FILE, to_node, msg))
2212 self.proc.LogWarning(msg)
2215 result = self.rpc.call_node_start_master(master, False, False)
2216 msg = result.fail_msg
2218 self.LogWarning("Could not re-enable the master role on"
2219 " the master, please restart manually: %s", msg)
2222 def _RecursiveCheckIfLVMBased(disk):
2223 """Check if the given disk or its children are lvm-based.
2225 @type disk: L{objects.Disk}
2226 @param disk: the disk to check
2228 @return: boolean indicating whether a LD_LV dev_type was found or not
2232 for chdisk in disk.children:
2233 if _RecursiveCheckIfLVMBased(chdisk):
2235 return disk.dev_type == constants.LD_LV
2238 class LUSetClusterParams(LogicalUnit):
2239 """Change the parameters of the cluster.
2242 HPATH = "cluster-modify"
2243 HTYPE = constants.HTYPE_CLUSTER
2247 def CheckArguments(self):
2251 for attr in ["candidate_pool_size",
2252 "uid_pool", "add_uids", "remove_uids"]:
2253 if not hasattr(self.op, attr):
2254 setattr(self.op, attr, None)
2256 if self.op.candidate_pool_size is not None:
2258 self.op.candidate_pool_size = int(self.op.candidate_pool_size)
2259 except (ValueError, TypeError), err:
2260 raise errors.OpPrereqError("Invalid candidate_pool_size value: %s" %
2261 str(err), errors.ECODE_INVAL)
2262 if self.op.candidate_pool_size < 1:
2263 raise errors.OpPrereqError("At least one master candidate needed",
2266 _CheckBooleanOpField(self.op, "maintain_node_health")
2268 if self.op.uid_pool:
2269 uidpool.CheckUidPool(self.op.uid_pool)
2271 if self.op.add_uids:
2272 uidpool.CheckUidPool(self.op.add_uids)
2274 if self.op.remove_uids:
2275 uidpool.CheckUidPool(self.op.remove_uids)
2277 def ExpandNames(self):
2278 # FIXME: in the future maybe other cluster params won't require checking on
2279 # all nodes to be modified.
2280 self.needed_locks = {
2281 locking.LEVEL_NODE: locking.ALL_SET,
2283 self.share_locks[locking.LEVEL_NODE] = 1
2285 def BuildHooksEnv(self):
2290 "OP_TARGET": self.cfg.GetClusterName(),
2291 "NEW_VG_NAME": self.op.vg_name,
2293 mn = self.cfg.GetMasterNode()
2294 return env, [mn], [mn]
2296 def CheckPrereq(self):
2297 """Check prerequisites.
2299 This checks whether the given params don't conflict and
2300 if the given volume group is valid.
2303 if self.op.vg_name is not None and not self.op.vg_name:
2304 instances = self.cfg.GetAllInstancesInfo().values()
2305 for inst in instances:
2306 for disk in inst.disks:
2307 if _RecursiveCheckIfLVMBased(disk):
2308 raise errors.OpPrereqError("Cannot disable lvm storage while"
2309 " lvm-based instances exist",
2312 node_list = self.acquired_locks[locking.LEVEL_NODE]
2314 # if vg_name not None, checks given volume group on all nodes
2316 vglist = self.rpc.call_vg_list(node_list)
2317 for node in node_list:
2318 msg = vglist[node].fail_msg
2320 # ignoring down node
2321 self.LogWarning("Error while gathering data on node %s"
2322 " (ignoring node): %s", node, msg)
2324 vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
2326 constants.MIN_VG_SIZE)
2328 raise errors.OpPrereqError("Error on node '%s': %s" %
2329 (node, vgstatus), errors.ECODE_ENVIRON)
2331 self.cluster = cluster = self.cfg.GetClusterInfo()
2332 # validate params changes
2333 if self.op.beparams:
2334 utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
2335 self.new_beparams = objects.FillDict(
2336 cluster.beparams[constants.PP_DEFAULT], self.op.beparams)
2338 if self.op.nicparams:
2339 utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
2340 self.new_nicparams = objects.FillDict(
2341 cluster.nicparams[constants.PP_DEFAULT], self.op.nicparams)
2342 objects.NIC.CheckParameterSyntax(self.new_nicparams)
2345 # check all instances for consistency
2346 for instance in self.cfg.GetAllInstancesInfo().values():
2347 for nic_idx, nic in enumerate(instance.nics):
2348 params_copy = copy.deepcopy(nic.nicparams)
2349 params_filled = objects.FillDict(self.new_nicparams, params_copy)
2351 # check parameter syntax
2353 objects.NIC.CheckParameterSyntax(params_filled)
2354 except errors.ConfigurationError, err:
2355 nic_errors.append("Instance %s, nic/%d: %s" %
2356 (instance.name, nic_idx, err))
2358 # if we're moving instances to routed, check that they have an ip
2359 target_mode = params_filled[constants.NIC_MODE]
2360 if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
2361 nic_errors.append("Instance %s, nic/%d: routed nick with no ip" %
2362 (instance.name, nic_idx))
2364 raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
2365 "\n".join(nic_errors))
2367 # hypervisor list/parameters
2368 self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
2369 if self.op.hvparams:
2370 if not isinstance(self.op.hvparams, dict):
2371 raise errors.OpPrereqError("Invalid 'hvparams' parameter on input",
2373 for hv_name, hv_dict in self.op.hvparams.items():
2374 if hv_name not in self.new_hvparams:
2375 self.new_hvparams[hv_name] = hv_dict
2377 self.new_hvparams[hv_name].update(hv_dict)
2379 # os hypervisor parameters
2380 self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
2382 if not isinstance(self.op.os_hvp, dict):
2383 raise errors.OpPrereqError("Invalid 'os_hvp' parameter on input",
2385 for os_name, hvs in self.op.os_hvp.items():
2386 if not isinstance(hvs, dict):
2387 raise errors.OpPrereqError(("Invalid 'os_hvp' parameter on"
2388 " input"), errors.ECODE_INVAL)
2389 if os_name not in self.new_os_hvp:
2390 self.new_os_hvp[os_name] = hvs
2392 for hv_name, hv_dict in hvs.items():
2393 if hv_name not in self.new_os_hvp[os_name]:
2394 self.new_os_hvp[os_name][hv_name] = hv_dict
2396 self.new_os_hvp[os_name][hv_name].update(hv_dict)
2398 # changes to the hypervisor list
2399 if self.op.enabled_hypervisors is not None:
2400 self.hv_list = self.op.enabled_hypervisors
2401 if not self.hv_list:
2402 raise errors.OpPrereqError("Enabled hypervisors list must contain at"
2403 " least one member",
2405 invalid_hvs = set(self.hv_list) - constants.HYPER_TYPES
2407 raise errors.OpPrereqError("Enabled hypervisors contains invalid"
2409 utils.CommaJoin(invalid_hvs),
2411 for hv in self.hv_list:
2412 # if the hypervisor doesn't already exist in the cluster
2413 # hvparams, we initialize it to empty, and then (in both
2414 # cases) we make sure to fill the defaults, as we might not
2415 # have a complete defaults list if the hypervisor wasn't
2417 if hv not in new_hvp:
2419 new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv])
2420 utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES)
2422 self.hv_list = cluster.enabled_hypervisors
2424 if self.op.hvparams or self.op.enabled_hypervisors is not None:
2425 # either the enabled list has changed, or the parameters have, validate
2426 for hv_name, hv_params in self.new_hvparams.items():
2427 if ((self.op.hvparams and hv_name in self.op.hvparams) or
2428 (self.op.enabled_hypervisors and
2429 hv_name in self.op.enabled_hypervisors)):
2430 # either this is a new hypervisor, or its parameters have changed
2431 hv_class = hypervisor.GetHypervisor(hv_name)
2432 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2433 hv_class.CheckParameterSyntax(hv_params)
2434 _CheckHVParams(self, node_list, hv_name, hv_params)
2437 # no need to check any newly-enabled hypervisors, since the
2438 # defaults have already been checked in the above code-block
2439 for os_name, os_hvp in self.new_os_hvp.items():
2440 for hv_name, hv_params in os_hvp.items():
2441 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2442 # we need to fill in the new os_hvp on top of the actual hv_p
2443 cluster_defaults = self.new_hvparams.get(hv_name, {})
2444 new_osp = objects.FillDict(cluster_defaults, hv_params)
2445 hv_class = hypervisor.GetHypervisor(hv_name)
2446 hv_class.CheckParameterSyntax(new_osp)
2447 _CheckHVParams(self, node_list, hv_name, new_osp)
2450 def Exec(self, feedback_fn):
2451 """Change the parameters of the cluster.
2454 if self.op.vg_name is not None:
2455 new_volume = self.op.vg_name
2458 if new_volume != self.cfg.GetVGName():
2459 self.cfg.SetVGName(new_volume)
2461 feedback_fn("Cluster LVM configuration already in desired"
2462 " state, not changing")
2463 if self.op.hvparams:
2464 self.cluster.hvparams = self.new_hvparams
2466 self.cluster.os_hvp = self.new_os_hvp
2467 if self.op.enabled_hypervisors is not None:
2468 self.cluster.hvparams = self.new_hvparams
2469 self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
2470 if self.op.beparams:
2471 self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
2472 if self.op.nicparams:
2473 self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
2475 if self.op.candidate_pool_size is not None:
2476 self.cluster.candidate_pool_size = self.op.candidate_pool_size
2477 # we need to update the pool size here, otherwise the save will fail
2478 _AdjustCandidatePool(self, [])
2480 if self.op.maintain_node_health is not None:
2481 self.cluster.maintain_node_health = self.op.maintain_node_health
2483 if self.op.add_uids is not None:
2484 uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
2486 if self.op.remove_uids is not None:
2487 uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
2489 if self.op.uid_pool is not None:
2490 self.cluster.uid_pool = self.op.uid_pool
2492 self.cfg.Update(self.cluster, feedback_fn)
2495 def _RedistributeAncillaryFiles(lu, additional_nodes=None):
2496 """Distribute additional files which are part of the cluster configuration.
2498 ConfigWriter takes care of distributing the config and ssconf files, but
2499 there are more files which should be distributed to all nodes. This function
2500 makes sure those are copied.
2502 @param lu: calling logical unit
2503 @param additional_nodes: list of nodes not in the config to distribute to
2506 # 1. Gather target nodes
2507 myself = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
2508 dist_nodes = lu.cfg.GetOnlineNodeList()
2509 if additional_nodes is not None:
2510 dist_nodes.extend(additional_nodes)
2511 if myself.name in dist_nodes:
2512 dist_nodes.remove(myself.name)
2514 # 2. Gather files to distribute
2515 dist_files = set([constants.ETC_HOSTS,
2516 constants.SSH_KNOWN_HOSTS_FILE,
2517 constants.RAPI_CERT_FILE,
2518 constants.RAPI_USERS_FILE,
2519 constants.CONFD_HMAC_KEY,
2522 enabled_hypervisors = lu.cfg.GetClusterInfo().enabled_hypervisors
2523 for hv_name in enabled_hypervisors:
2524 hv_class = hypervisor.GetHypervisor(hv_name)
2525 dist_files.update(hv_class.GetAncillaryFiles())
2527 # 3. Perform the files upload
2528 for fname in dist_files:
2529 if os.path.exists(fname):
2530 result = lu.rpc.call_upload_file(dist_nodes, fname)
2531 for to_node, to_result in result.items():
2532 msg = to_result.fail_msg
2534 msg = ("Copy of file %s to node %s failed: %s" %
2535 (fname, to_node, msg))
2536 lu.proc.LogWarning(msg)
2539 class LURedistributeConfig(NoHooksLU):
2540 """Force the redistribution of cluster configuration.
2542 This is a very simple LU.
2548 def ExpandNames(self):
2549 self.needed_locks = {
2550 locking.LEVEL_NODE: locking.ALL_SET,
2552 self.share_locks[locking.LEVEL_NODE] = 1
2554 def CheckPrereq(self):
2555 """Check prerequisites.
2559 def Exec(self, feedback_fn):
2560 """Redistribute the configuration.
2563 self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
2564 _RedistributeAncillaryFiles(self)
2567 def _WaitForSync(lu, instance, oneshot=False):
2568 """Sleep and poll for an instance's disk to sync.
2571 if not instance.disks:
2575 lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
2577 node = instance.primary_node
2579 for dev in instance.disks:
2580 lu.cfg.SetDiskID(dev, node)
2582 # TODO: Convert to utils.Retry
2585 degr_retries = 10 # in seconds, as we sleep 1 second each time
2589 cumul_degraded = False
2590 rstats = lu.rpc.call_blockdev_getmirrorstatus(node, instance.disks)
2591 msg = rstats.fail_msg
2593 lu.LogWarning("Can't get any data from node %s: %s", node, msg)
2596 raise errors.RemoteError("Can't contact node %s for mirror data,"
2597 " aborting." % node)
2600 rstats = rstats.payload
2602 for i, mstat in enumerate(rstats):
2604 lu.LogWarning("Can't compute data for node %s/%s",
2605 node, instance.disks[i].iv_name)
2608 cumul_degraded = (cumul_degraded or
2609 (mstat.is_degraded and mstat.sync_percent is None))
2610 if mstat.sync_percent is not None:
2612 if mstat.estimated_time is not None:
2613 rem_time = "%d estimated seconds remaining" % mstat.estimated_time
2614 max_time = mstat.estimated_time
2616 rem_time = "no time estimate"
2617 lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
2618 (instance.disks[i].iv_name, mstat.sync_percent,
2621 # if we're done but degraded, let's do a few small retries, to
2622 # make sure we see a stable and not transient situation; therefore
2623 # we force restart of the loop
2624 if (done or oneshot) and cumul_degraded and degr_retries > 0:
2625 logging.info("Degraded disks found, %d retries left", degr_retries)
2633 time.sleep(min(60, max_time))
2636 lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
2637 return not cumul_degraded
2640 def _CheckDiskConsistency(lu, dev, node, on_primary, ldisk=False):
2641 """Check that mirrors are not degraded.
2643 The ldisk parameter, if True, will change the test from the
2644 is_degraded attribute (which represents overall non-ok status for
2645 the device(s)) to the ldisk (representing the local storage status).
2648 lu.cfg.SetDiskID(dev, node)
2652 if on_primary or dev.AssembleOnSecondary():
2653 rstats = lu.rpc.call_blockdev_find(node, dev)
2654 msg = rstats.fail_msg
2656 lu.LogWarning("Can't find disk on node %s: %s", node, msg)
2658 elif not rstats.payload:
2659 lu.LogWarning("Can't find disk on node %s", node)
2663 result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
2665 result = result and not rstats.payload.is_degraded
2668 for child in dev.children:
2669 result = result and _CheckDiskConsistency(lu, child, node, on_primary)
2674 class LUDiagnoseOS(NoHooksLU):
2675 """Logical unit for OS diagnose/query.
2678 _OP_REQP = ["output_fields", "names"]
2680 _FIELDS_STATIC = utils.FieldSet()
2681 _FIELDS_DYNAMIC = utils.FieldSet("name", "valid", "node_status", "variants")
2682 # Fields that need calculation of global os validity
2683 _FIELDS_NEEDVALID = frozenset(["valid", "variants"])
2685 def ExpandNames(self):
2687 raise errors.OpPrereqError("Selective OS query not supported",
2690 _CheckOutputFields(static=self._FIELDS_STATIC,
2691 dynamic=self._FIELDS_DYNAMIC,
2692 selected=self.op.output_fields)
2694 # Lock all nodes, in shared mode
2695 # Temporary removal of locks, should be reverted later
2696 # TODO: reintroduce locks when they are lighter-weight
2697 self.needed_locks = {}
2698 #self.share_locks[locking.LEVEL_NODE] = 1
2699 #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
2701 def CheckPrereq(self):
2702 """Check prerequisites.
2707 def _DiagnoseByOS(rlist):
2708 """Remaps a per-node return list into an a per-os per-node dictionary
2710 @param rlist: a map with node names as keys and OS objects as values
2713 @return: a dictionary with osnames as keys and as value another map, with
2714 nodes as keys and tuples of (path, status, diagnose) as values, eg::
2716 {"debian-etch": {"node1": [(/usr/lib/..., True, ""),
2717 (/srv/..., False, "invalid api")],
2718 "node2": [(/srv/..., True, "")]}
2723 # we build here the list of nodes that didn't fail the RPC (at RPC
2724 # level), so that nodes with a non-responding node daemon don't
2725 # make all OSes invalid
2726 good_nodes = [node_name for node_name in rlist
2727 if not rlist[node_name].fail_msg]
2728 for node_name, nr in rlist.items():
2729 if nr.fail_msg or not nr.payload:
2731 for name, path, status, diagnose, variants in nr.payload:
2732 if name not in all_os:
2733 # build a list of nodes for this os containing empty lists
2734 # for each node in node_list
2736 for nname in good_nodes:
2737 all_os[name][nname] = []
2738 all_os[name][node_name].append((path, status, diagnose, variants))
2741 def Exec(self, feedback_fn):
2742 """Compute the list of OSes.
2745 valid_nodes = [node for node in self.cfg.GetOnlineNodeList()]
2746 node_data = self.rpc.call_os_diagnose(valid_nodes)
2747 pol = self._DiagnoseByOS(node_data)
2749 calc_valid = self._FIELDS_NEEDVALID.intersection(self.op.output_fields)
2750 calc_variants = "variants" in self.op.output_fields
2752 for os_name, os_data in pol.items():
2757 for osl in os_data.values():
2758 valid = valid and osl and osl[0][1]
2763 node_variants = osl[0][3]
2764 if variants is None:
2765 variants = node_variants
2767 variants = [v for v in variants if v in node_variants]
2769 for field in self.op.output_fields:
2772 elif field == "valid":
2774 elif field == "node_status":
2775 # this is just a copy of the dict
2777 for node_name, nos_list in os_data.items():
2778 val[node_name] = nos_list
2779 elif field == "variants":
2782 raise errors.ParameterError(field)
2789 class LURemoveNode(LogicalUnit):
2790 """Logical unit for removing a node.
2793 HPATH = "node-remove"
2794 HTYPE = constants.HTYPE_NODE
2795 _OP_REQP = ["node_name"]
2797 def BuildHooksEnv(self):
2800 This doesn't run on the target node in the pre phase as a failed
2801 node would then be impossible to remove.
2805 "OP_TARGET": self.op.node_name,
2806 "NODE_NAME": self.op.node_name,
2808 all_nodes = self.cfg.GetNodeList()
2810 all_nodes.remove(self.op.node_name)
2812 logging.warning("Node %s which is about to be removed not found"
2813 " in the all nodes list", self.op.node_name)
2814 return env, all_nodes, all_nodes
2816 def CheckPrereq(self):
2817 """Check prerequisites.
2820 - the node exists in the configuration
2821 - it does not have primary or secondary instances
2822 - it's not the master
2824 Any errors are signaled by raising errors.OpPrereqError.
2827 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
2828 node = self.cfg.GetNodeInfo(self.op.node_name)
2829 assert node is not None
2831 instance_list = self.cfg.GetInstanceList()
2833 masternode = self.cfg.GetMasterNode()
2834 if node.name == masternode:
2835 raise errors.OpPrereqError("Node is the master node,"
2836 " you need to failover first.",
2839 for instance_name in instance_list:
2840 instance = self.cfg.GetInstanceInfo(instance_name)
2841 if node.name in instance.all_nodes:
2842 raise errors.OpPrereqError("Instance %s is still running on the node,"
2843 " please remove first." % instance_name,
2845 self.op.node_name = node.name
2848 def Exec(self, feedback_fn):
2849 """Removes the node from the cluster.
2853 logging.info("Stopping the node daemon and removing configs from node %s",
2856 modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
2858 # Promote nodes to master candidate as needed
2859 _AdjustCandidatePool(self, exceptions=[node.name])
2860 self.context.RemoveNode(node.name)
2862 # Run post hooks on the node before it's removed
2863 hm = self.proc.hmclass(self.rpc.call_hooks_runner, self)
2865 hm.RunPhase(constants.HOOKS_PHASE_POST, [node.name])
2867 # pylint: disable-msg=W0702
2868 self.LogWarning("Errors occurred running hooks on %s" % node.name)
2870 result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
2871 msg = result.fail_msg
2873 self.LogWarning("Errors encountered on the remote node while leaving"
2874 " the cluster: %s", msg)
2876 # Remove node from our /etc/hosts
2877 if self.cfg.GetClusterInfo().modify_etc_hosts:
2878 # FIXME: this should be done via an rpc call to node daemon
2879 utils.RemoveHostFromEtcHosts(node.name)
2880 _RedistributeAncillaryFiles(self)
2883 class LUQueryNodes(NoHooksLU):
2884 """Logical unit for querying nodes.
2887 # pylint: disable-msg=W0142
2888 _OP_REQP = ["output_fields", "names", "use_locking"]
2891 _SIMPLE_FIELDS = ["name", "serial_no", "ctime", "mtime", "uuid",
2892 "master_candidate", "offline", "drained"]
2894 _FIELDS_DYNAMIC = utils.FieldSet(
2896 "mtotal", "mnode", "mfree",
2898 "ctotal", "cnodes", "csockets",
2901 _FIELDS_STATIC = utils.FieldSet(*[
2902 "pinst_cnt", "sinst_cnt",
2903 "pinst_list", "sinst_list",
2904 "pip", "sip", "tags",
2906 "role"] + _SIMPLE_FIELDS
2909 def ExpandNames(self):
2910 _CheckOutputFields(static=self._FIELDS_STATIC,
2911 dynamic=self._FIELDS_DYNAMIC,
2912 selected=self.op.output_fields)
2914 self.needed_locks = {}
2915 self.share_locks[locking.LEVEL_NODE] = 1
2918 self.wanted = _GetWantedNodes(self, self.op.names)
2920 self.wanted = locking.ALL_SET
2922 self.do_node_query = self._FIELDS_STATIC.NonMatching(self.op.output_fields)
2923 self.do_locking = self.do_node_query and self.op.use_locking
2925 # if we don't request only static fields, we need to lock the nodes
2926 self.needed_locks[locking.LEVEL_NODE] = self.wanted
2928 def CheckPrereq(self):
2929 """Check prerequisites.
2932 # The validation of the node list is done in the _GetWantedNodes,
2933 # if non empty, and if empty, there's no validation to do
2936 def Exec(self, feedback_fn):
2937 """Computes the list of nodes and their attributes.
2940 all_info = self.cfg.GetAllNodesInfo()
2942 nodenames = self.acquired_locks[locking.LEVEL_NODE]
2943 elif self.wanted != locking.ALL_SET:
2944 nodenames = self.wanted
2945 missing = set(nodenames).difference(all_info.keys())
2947 raise errors.OpExecError(
2948 "Some nodes were removed before retrieving their data: %s" % missing)
2950 nodenames = all_info.keys()
2952 nodenames = utils.NiceSort(nodenames)
2953 nodelist = [all_info[name] for name in nodenames]
2955 # begin data gathering
2957 if self.do_node_query:
2959 node_data = self.rpc.call_node_info(nodenames, self.cfg.GetVGName(),
2960 self.cfg.GetHypervisorType())
2961 for name in nodenames:
2962 nodeinfo = node_data[name]
2963 if not nodeinfo.fail_msg and nodeinfo.payload:
2964 nodeinfo = nodeinfo.payload
2965 fn = utils.TryConvert
2967 "mtotal": fn(int, nodeinfo.get('memory_total', None)),
2968 "mnode": fn(int, nodeinfo.get('memory_dom0', None)),
2969 "mfree": fn(int, nodeinfo.get('memory_free', None)),
2970 "dtotal": fn(int, nodeinfo.get('vg_size', None)),
2971 "dfree": fn(int, nodeinfo.get('vg_free', None)),
2972 "ctotal": fn(int, nodeinfo.get('cpu_total', None)),
2973 "bootid": nodeinfo.get('bootid', None),
2974 "cnodes": fn(int, nodeinfo.get('cpu_nodes', None)),
2975 "csockets": fn(int, nodeinfo.get('cpu_sockets', None)),
2978 live_data[name] = {}
2980 live_data = dict.fromkeys(nodenames, {})
2982 node_to_primary = dict([(name, set()) for name in nodenames])
2983 node_to_secondary = dict([(name, set()) for name in nodenames])
2985 inst_fields = frozenset(("pinst_cnt", "pinst_list",
2986 "sinst_cnt", "sinst_list"))
2987 if inst_fields & frozenset(self.op.output_fields):
2988 inst_data = self.cfg.GetAllInstancesInfo()
2990 for inst in inst_data.values():
2991 if inst.primary_node in node_to_primary:
2992 node_to_primary[inst.primary_node].add(inst.name)
2993 for secnode in inst.secondary_nodes:
2994 if secnode in node_to_secondary:
2995 node_to_secondary[secnode].add(inst.name)
2997 master_node = self.cfg.GetMasterNode()
2999 # end data gathering
3002 for node in nodelist:
3004 for field in self.op.output_fields:
3005 if field in self._SIMPLE_FIELDS:
3006 val = getattr(node, field)
3007 elif field == "pinst_list":
3008 val = list(node_to_primary[node.name])
3009 elif field == "sinst_list":
3010 val = list(node_to_secondary[node.name])
3011 elif field == "pinst_cnt":
3012 val = len(node_to_primary[node.name])
3013 elif field == "sinst_cnt":
3014 val = len(node_to_secondary[node.name])
3015 elif field == "pip":
3016 val = node.primary_ip
3017 elif field == "sip":
3018 val = node.secondary_ip
3019 elif field == "tags":
3020 val = list(node.GetTags())
3021 elif field == "master":
3022 val = node.name == master_node
3023 elif self._FIELDS_DYNAMIC.Matches(field):
3024 val = live_data[node.name].get(field, None)
3025 elif field == "role":
3026 if node.name == master_node:
3028 elif node.master_candidate:
3037 raise errors.ParameterError(field)
3038 node_output.append(val)
3039 output.append(node_output)
3044 class LUQueryNodeVolumes(NoHooksLU):
3045 """Logical unit for getting volumes on node(s).
3048 _OP_REQP = ["nodes", "output_fields"]
3050 _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
3051 _FIELDS_STATIC = utils.FieldSet("node")
3053 def ExpandNames(self):
3054 _CheckOutputFields(static=self._FIELDS_STATIC,
3055 dynamic=self._FIELDS_DYNAMIC,
3056 selected=self.op.output_fields)
3058 self.needed_locks = {}
3059 self.share_locks[locking.LEVEL_NODE] = 1
3060 if not self.op.nodes:
3061 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3063 self.needed_locks[locking.LEVEL_NODE] = \
3064 _GetWantedNodes(self, self.op.nodes)
3066 def CheckPrereq(self):
3067 """Check prerequisites.
3069 This checks that the fields required are valid output fields.
3072 self.nodes = self.acquired_locks[locking.LEVEL_NODE]
3074 def Exec(self, feedback_fn):
3075 """Computes the list of nodes and their attributes.
3078 nodenames = self.nodes
3079 volumes = self.rpc.call_node_volumes(nodenames)
3081 ilist = [self.cfg.GetInstanceInfo(iname) for iname
3082 in self.cfg.GetInstanceList()]
3084 lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist])
3087 for node in nodenames:
3088 nresult = volumes[node]
3091 msg = nresult.fail_msg
3093 self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
3096 node_vols = nresult.payload[:]
3097 node_vols.sort(key=lambda vol: vol['dev'])
3099 for vol in node_vols:
3101 for field in self.op.output_fields:
3104 elif field == "phys":
3108 elif field == "name":
3110 elif field == "size":
3111 val = int(float(vol['size']))
3112 elif field == "instance":
3114 if node not in lv_by_node[inst]:
3116 if vol['name'] in lv_by_node[inst][node]:
3122 raise errors.ParameterError(field)
3123 node_output.append(str(val))
3125 output.append(node_output)
3130 class LUQueryNodeStorage(NoHooksLU):
3131 """Logical unit for getting information on storage units on node(s).
3134 _OP_REQP = ["nodes", "storage_type", "output_fields"]
3136 _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
3138 def CheckArguments(self):
3139 _CheckStorageType(self.op.storage_type)
3141 _CheckOutputFields(static=self._FIELDS_STATIC,
3142 dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
3143 selected=self.op.output_fields)
3145 def ExpandNames(self):
3146 self.needed_locks = {}
3147 self.share_locks[locking.LEVEL_NODE] = 1
3150 self.needed_locks[locking.LEVEL_NODE] = \
3151 _GetWantedNodes(self, self.op.nodes)
3153 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3155 def CheckPrereq(self):
3156 """Check prerequisites.
3158 This checks that the fields required are valid output fields.
3161 self.op.name = getattr(self.op, "name", None)
3163 self.nodes = self.acquired_locks[locking.LEVEL_NODE]
3165 def Exec(self, feedback_fn):
3166 """Computes the list of nodes and their attributes.
3169 # Always get name to sort by
3170 if constants.SF_NAME in self.op.output_fields:
3171 fields = self.op.output_fields[:]
3173 fields = [constants.SF_NAME] + self.op.output_fields
3175 # Never ask for node or type as it's only known to the LU
3176 for extra in [constants.SF_NODE, constants.SF_TYPE]:
3177 while extra in fields:
3178 fields.remove(extra)
3180 field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
3181 name_idx = field_idx[constants.SF_NAME]
3183 st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
3184 data = self.rpc.call_storage_list(self.nodes,
3185 self.op.storage_type, st_args,
3186 self.op.name, fields)
3190 for node in utils.NiceSort(self.nodes):
3191 nresult = data[node]
3195 msg = nresult.fail_msg
3197 self.LogWarning("Can't get storage data from node %s: %s", node, msg)
3200 rows = dict([(row[name_idx], row) for row in nresult.payload])
3202 for name in utils.NiceSort(rows.keys()):
3207 for field in self.op.output_fields:
3208 if field == constants.SF_NODE:
3210 elif field == constants.SF_TYPE:
3211 val = self.op.storage_type
3212 elif field in field_idx:
3213 val = row[field_idx[field]]
3215 raise errors.ParameterError(field)
3224 class LUModifyNodeStorage(NoHooksLU):
3225 """Logical unit for modifying a storage volume on a node.
3228 _OP_REQP = ["node_name", "storage_type", "name", "changes"]
3231 def CheckArguments(self):
3232 self.opnode_name = _ExpandNodeName(self.cfg, self.op.node_name)
3234 _CheckStorageType(self.op.storage_type)
3236 def ExpandNames(self):
3237 self.needed_locks = {
3238 locking.LEVEL_NODE: self.op.node_name,
3241 def CheckPrereq(self):
3242 """Check prerequisites.
3245 storage_type = self.op.storage_type
3248 modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
3250 raise errors.OpPrereqError("Storage units of type '%s' can not be"
3251 " modified" % storage_type,
3254 diff = set(self.op.changes.keys()) - modifiable
3256 raise errors.OpPrereqError("The following fields can not be modified for"
3257 " storage units of type '%s': %r" %
3258 (storage_type, list(diff)),
3261 def Exec(self, feedback_fn):
3262 """Computes the list of nodes and their attributes.
3265 st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
3266 result = self.rpc.call_storage_modify(self.op.node_name,
3267 self.op.storage_type, st_args,
3268 self.op.name, self.op.changes)
3269 result.Raise("Failed to modify storage unit '%s' on %s" %
3270 (self.op.name, self.op.node_name))
3273 class LUAddNode(LogicalUnit):
3274 """Logical unit for adding node to the cluster.
3278 HTYPE = constants.HTYPE_NODE
3279 _OP_REQP = ["node_name"]
3281 def CheckArguments(self):
3282 # validate/normalize the node name
3283 self.op.node_name = utils.HostInfo.NormalizeName(self.op.node_name)
3285 def BuildHooksEnv(self):
3288 This will run on all nodes before, and on all nodes + the new node after.
3292 "OP_TARGET": self.op.node_name,
3293 "NODE_NAME": self.op.node_name,
3294 "NODE_PIP": self.op.primary_ip,
3295 "NODE_SIP": self.op.secondary_ip,
3297 nodes_0 = self.cfg.GetNodeList()
3298 nodes_1 = nodes_0 + [self.op.node_name, ]
3299 return env, nodes_0, nodes_1
3301 def CheckPrereq(self):
3302 """Check prerequisites.
3305 - the new node is not already in the config
3307 - its parameters (single/dual homed) matches the cluster
3309 Any errors are signaled by raising errors.OpPrereqError.
3312 node_name = self.op.node_name
3315 dns_data = utils.GetHostInfo(node_name)
3317 node = dns_data.name
3318 primary_ip = self.op.primary_ip = dns_data.ip
3319 secondary_ip = getattr(self.op, "secondary_ip", None)
3320 if secondary_ip is None:
3321 secondary_ip = primary_ip
3322 if not utils.IsValidIP(secondary_ip):
3323 raise errors.OpPrereqError("Invalid secondary IP given",
3325 self.op.secondary_ip = secondary_ip
3327 node_list = cfg.GetNodeList()
3328 if not self.op.readd and node in node_list:
3329 raise errors.OpPrereqError("Node %s is already in the configuration" %
3330 node, errors.ECODE_EXISTS)
3331 elif self.op.readd and node not in node_list:
3332 raise errors.OpPrereqError("Node %s is not in the configuration" % node,
3335 self.changed_primary_ip = False
3337 for existing_node_name in node_list:
3338 existing_node = cfg.GetNodeInfo(existing_node_name)
3340 if self.op.readd and node == existing_node_name:
3341 if existing_node.secondary_ip != secondary_ip:
3342 raise errors.OpPrereqError("Readded node doesn't have the same IP"
3343 " address configuration as before",
3345 if existing_node.primary_ip != primary_ip:
3346 self.changed_primary_ip = True
3350 if (existing_node.primary_ip == primary_ip or
3351 existing_node.secondary_ip == primary_ip or
3352 existing_node.primary_ip == secondary_ip or
3353 existing_node.secondary_ip == secondary_ip):
3354 raise errors.OpPrereqError("New node ip address(es) conflict with"
3355 " existing node %s" % existing_node.name,
3356 errors.ECODE_NOTUNIQUE)
3358 # check that the type of the node (single versus dual homed) is the
3359 # same as for the master
3360 myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
3361 master_singlehomed = myself.secondary_ip == myself.primary_ip
3362 newbie_singlehomed = secondary_ip == primary_ip
3363 if master_singlehomed != newbie_singlehomed:
3364 if master_singlehomed:
3365 raise errors.OpPrereqError("The master has no private ip but the"
3366 " new node has one",
3369 raise errors.OpPrereqError("The master has a private ip but the"
3370 " new node doesn't have one",
3373 # checks reachability
3374 if not utils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
3375 raise errors.OpPrereqError("Node not reachable by ping",
3376 errors.ECODE_ENVIRON)
3378 if not newbie_singlehomed:
3379 # check reachability from my secondary ip to newbie's secondary ip
3380 if not utils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
3381 source=myself.secondary_ip):
3382 raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
3383 " based ping to noded port",
3384 errors.ECODE_ENVIRON)
3391 self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
3394 self.new_node = self.cfg.GetNodeInfo(node)
3395 assert self.new_node is not None, "Can't retrieve locked node %s" % node
3397 self.new_node = objects.Node(name=node,
3398 primary_ip=primary_ip,
3399 secondary_ip=secondary_ip,
3400 master_candidate=self.master_candidate,
3401 offline=False, drained=False)
3403 def Exec(self, feedback_fn):
3404 """Adds the new node to the cluster.
3407 new_node = self.new_node
3408 node = new_node.name
3410 # for re-adds, reset the offline/drained/master-candidate flags;
3411 # we need to reset here, otherwise offline would prevent RPC calls
3412 # later in the procedure; this also means that if the re-add
3413 # fails, we are left with a non-offlined, broken node
3415 new_node.drained = new_node.offline = False # pylint: disable-msg=W0201
3416 self.LogInfo("Readding a node, the offline/drained flags were reset")
3417 # if we demote the node, we do cleanup later in the procedure
3418 new_node.master_candidate = self.master_candidate
3419 if self.changed_primary_ip:
3420 new_node.primary_ip = self.op.primary_ip
3422 # notify the user about any possible mc promotion
3423 if new_node.master_candidate:
3424 self.LogInfo("Node will be a master candidate")
3426 # check connectivity
3427 result = self.rpc.call_version([node])[node]
3428 result.Raise("Can't get version information from node %s" % node)
3429 if constants.PROTOCOL_VERSION == result.payload:
3430 logging.info("Communication to node %s fine, sw version %s match",
3431 node, result.payload)
3433 raise errors.OpExecError("Version mismatch master version %s,"
3434 " node version %s" %
3435 (constants.PROTOCOL_VERSION, result.payload))
3438 if self.cfg.GetClusterInfo().modify_ssh_setup:
3439 logging.info("Copy ssh key to node %s", node)
3440 priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
3442 keyfiles = [constants.SSH_HOST_DSA_PRIV, constants.SSH_HOST_DSA_PUB,
3443 constants.SSH_HOST_RSA_PRIV, constants.SSH_HOST_RSA_PUB,
3447 keyarray.append(utils.ReadFile(i))
3449 result = self.rpc.call_node_add(node, keyarray[0], keyarray[1],
3450 keyarray[2], keyarray[3], keyarray[4],
3452 result.Raise("Cannot transfer ssh keys to the new node")
3454 # Add node to our /etc/hosts, and add key to known_hosts
3455 if self.cfg.GetClusterInfo().modify_etc_hosts:
3456 # FIXME: this should be done via an rpc call to node daemon
3457 utils.AddHostToEtcHosts(new_node.name)
3459 if new_node.secondary_ip != new_node.primary_ip:
3460 result = self.rpc.call_node_has_ip_address(new_node.name,
3461 new_node.secondary_ip)
3462 result.Raise("Failure checking secondary ip on node %s" % new_node.name,
3463 prereq=True, ecode=errors.ECODE_ENVIRON)
3464 if not result.payload:
3465 raise errors.OpExecError("Node claims it doesn't have the secondary ip"
3466 " you gave (%s). Please fix and re-run this"
3467 " command." % new_node.secondary_ip)
3469 node_verify_list = [self.cfg.GetMasterNode()]
3470 node_verify_param = {
3471 constants.NV_NODELIST: [node],
3472 # TODO: do a node-net-test as well?
3475 result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
3476 self.cfg.GetClusterName())
3477 for verifier in node_verify_list:
3478 result[verifier].Raise("Cannot communicate with node %s" % verifier)
3479 nl_payload = result[verifier].payload[constants.NV_NODELIST]
3481 for failed in nl_payload:
3482 feedback_fn("ssh/hostname verification failed"
3483 " (checking from %s): %s" %
3484 (verifier, nl_payload[failed]))
3485 raise errors.OpExecError("ssh/hostname verification failed.")
3488 _RedistributeAncillaryFiles(self)
3489 self.context.ReaddNode(new_node)
3490 # make sure we redistribute the config
3491 self.cfg.Update(new_node, feedback_fn)
3492 # and make sure the new node will not have old files around
3493 if not new_node.master_candidate:
3494 result = self.rpc.call_node_demote_from_mc(new_node.name)
3495 msg = result.fail_msg
3497 self.LogWarning("Node failed to demote itself from master"
3498 " candidate status: %s" % msg)
3500 _RedistributeAncillaryFiles(self, additional_nodes=[node])
3501 self.context.AddNode(new_node, self.proc.GetECId())
3504 class LUSetNodeParams(LogicalUnit):
3505 """Modifies the parameters of a node.
3508 HPATH = "node-modify"
3509 HTYPE = constants.HTYPE_NODE
3510 _OP_REQP = ["node_name"]
3513 def CheckArguments(self):
3514 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
3515 _CheckBooleanOpField(self.op, 'master_candidate')
3516 _CheckBooleanOpField(self.op, 'offline')
3517 _CheckBooleanOpField(self.op, 'drained')
3518 _CheckBooleanOpField(self.op, 'auto_promote')
3519 all_mods = [self.op.offline, self.op.master_candidate, self.op.drained]
3520 if all_mods.count(None) == 3:
3521 raise errors.OpPrereqError("Please pass at least one modification",
3523 if all_mods.count(True) > 1:
3524 raise errors.OpPrereqError("Can't set the node into more than one"
3525 " state at the same time",
3528 # Boolean value that tells us whether we're offlining or draining the node
3529 self.offline_or_drain = (self.op.offline == True or
3530 self.op.drained == True)
3531 self.deoffline_or_drain = (self.op.offline == False or
3532 self.op.drained == False)
3533 self.might_demote = (self.op.master_candidate == False or
3534 self.offline_or_drain)
3536 self.lock_all = self.op.auto_promote and self.might_demote
3539 def ExpandNames(self):
3541 self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
3543 self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
3545 def BuildHooksEnv(self):
3548 This runs on the master node.
3552 "OP_TARGET": self.op.node_name,
3553 "MASTER_CANDIDATE": str(self.op.master_candidate),
3554 "OFFLINE": str(self.op.offline),
3555 "DRAINED": str(self.op.drained),
3557 nl = [self.cfg.GetMasterNode(),
3561 def CheckPrereq(self):
3562 """Check prerequisites.
3564 This only checks the instance list against the existing names.
3567 node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
3569 if (self.op.master_candidate is not None or
3570 self.op.drained is not None or
3571 self.op.offline is not None):
3572 # we can't change the master's node flags
3573 if self.op.node_name == self.cfg.GetMasterNode():
3574 raise errors.OpPrereqError("The master role can be changed"
3575 " only via masterfailover",
3579 if node.master_candidate and self.might_demote and not self.lock_all:
3580 assert not self.op.auto_promote, "auto-promote set but lock_all not"
3581 # check if after removing the current node, we're missing master
3583 (mc_remaining, mc_should, _) = \
3584 self.cfg.GetMasterCandidateStats(exceptions=[node.name])
3585 if mc_remaining < mc_should:
3586 raise errors.OpPrereqError("Not enough master candidates, please"
3587 " pass auto_promote to allow promotion",
3590 if (self.op.master_candidate == True and
3591 ((node.offline and not self.op.offline == False) or
3592 (node.drained and not self.op.drained == False))):
3593 raise errors.OpPrereqError("Node '%s' is offline or drained, can't set"
3594 " to master_candidate" % node.name,
3597 # If we're being deofflined/drained, we'll MC ourself if needed
3598 if (self.deoffline_or_drain and not self.offline_or_drain and not
3599 self.op.master_candidate == True and not node.master_candidate):
3600 self.op.master_candidate = _DecideSelfPromotion(self)
3601 if self.op.master_candidate:
3602 self.LogInfo("Autopromoting node to master candidate")
3606 def Exec(self, feedback_fn):
3615 if self.op.offline is not None:
3616 node.offline = self.op.offline
3617 result.append(("offline", str(self.op.offline)))
3618 if self.op.offline == True:
3619 if node.master_candidate:
3620 node.master_candidate = False
3622 result.append(("master_candidate", "auto-demotion due to offline"))
3624 node.drained = False
3625 result.append(("drained", "clear drained status due to offline"))
3627 if self.op.master_candidate is not None:
3628 node.master_candidate = self.op.master_candidate
3630 result.append(("master_candidate", str(self.op.master_candidate)))
3631 if self.op.master_candidate == False:
3632 rrc = self.rpc.call_node_demote_from_mc(node.name)
3635 self.LogWarning("Node failed to demote itself: %s" % msg)
3637 if self.op.drained is not None:
3638 node.drained = self.op.drained
3639 result.append(("drained", str(self.op.drained)))
3640 if self.op.drained == True:
3641 if node.master_candidate:
3642 node.master_candidate = False
3644 result.append(("master_candidate", "auto-demotion due to drain"))
3645 rrc = self.rpc.call_node_demote_from_mc(node.name)
3648 self.LogWarning("Node failed to demote itself: %s" % msg)
3650 node.offline = False
3651 result.append(("offline", "clear offline status due to drain"))
3653 # we locked all nodes, we adjust the CP before updating this node
3655 _AdjustCandidatePool(self, [node.name])
3657 # this will trigger configuration file update, if needed
3658 self.cfg.Update(node, feedback_fn)
3660 # this will trigger job queue propagation or cleanup
3662 self.context.ReaddNode(node)
3667 class LUPowercycleNode(NoHooksLU):
3668 """Powercycles a node.
3671 _OP_REQP = ["node_name", "force"]
3674 def CheckArguments(self):
3675 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
3676 if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
3677 raise errors.OpPrereqError("The node is the master and the force"
3678 " parameter was not set",
3681 def ExpandNames(self):
3682 """Locking for PowercycleNode.
3684 This is a last-resort option and shouldn't block on other
3685 jobs. Therefore, we grab no locks.
3688 self.needed_locks = {}
3690 def CheckPrereq(self):
3691 """Check prerequisites.
3693 This LU has no prereqs.
3698 def Exec(self, feedback_fn):
3702 result = self.rpc.call_node_powercycle(self.op.node_name,
3703 self.cfg.GetHypervisorType())
3704 result.Raise("Failed to schedule the reboot")
3705 return result.payload
3708 class LUQueryClusterInfo(NoHooksLU):
3709 """Query cluster configuration.
3715 def ExpandNames(self):
3716 self.needed_locks = {}
3718 def CheckPrereq(self):
3719 """No prerequsites needed for this LU.
3724 def Exec(self, feedback_fn):
3725 """Return cluster config.
3728 cluster = self.cfg.GetClusterInfo()
3731 # Filter just for enabled hypervisors
3732 for os_name, hv_dict in cluster.os_hvp.items():
3733 os_hvp[os_name] = {}
3734 for hv_name, hv_params in hv_dict.items():
3735 if hv_name in cluster.enabled_hypervisors:
3736 os_hvp[os_name][hv_name] = hv_params
3739 "software_version": constants.RELEASE_VERSION,
3740 "protocol_version": constants.PROTOCOL_VERSION,
3741 "config_version": constants.CONFIG_VERSION,
3742 "os_api_version": max(constants.OS_API_VERSIONS),
3743 "export_version": constants.EXPORT_VERSION,
3744 "architecture": (platform.architecture()[0], platform.machine()),
3745 "name": cluster.cluster_name,
3746 "master": cluster.master_node,
3747 "default_hypervisor": cluster.enabled_hypervisors[0],
3748 "enabled_hypervisors": cluster.enabled_hypervisors,
3749 "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
3750 for hypervisor_name in cluster.enabled_hypervisors]),
3752 "beparams": cluster.beparams,
3753 "nicparams": cluster.nicparams,
3754 "candidate_pool_size": cluster.candidate_pool_size,
3755 "master_netdev": cluster.master_netdev,
3756 "volume_group_name": cluster.volume_group_name,
3757 "file_storage_dir": cluster.file_storage_dir,
3758 "maintain_node_health": cluster.maintain_node_health,
3759 "ctime": cluster.ctime,
3760 "mtime": cluster.mtime,
3761 "uuid": cluster.uuid,
3762 "tags": list(cluster.GetTags()),
3763 "uid_pool": cluster.uid_pool,
3769 class LUQueryConfigValues(NoHooksLU):
3770 """Return configuration values.
3775 _FIELDS_DYNAMIC = utils.FieldSet()
3776 _FIELDS_STATIC = utils.FieldSet("cluster_name", "master_node", "drain_flag",
3779 def ExpandNames(self):
3780 self.needed_locks = {}
3782 _CheckOutputFields(static=self._FIELDS_STATIC,
3783 dynamic=self._FIELDS_DYNAMIC,
3784 selected=self.op.output_fields)
3786 def CheckPrereq(self):
3787 """No prerequisites.
3792 def Exec(self, feedback_fn):
3793 """Dump a representation of the cluster config to the standard output.
3797 for field in self.op.output_fields:
3798 if field == "cluster_name":
3799 entry = self.cfg.GetClusterName()
3800 elif field == "master_node":
3801 entry = self.cfg.GetMasterNode()
3802 elif field == "drain_flag":
3803 entry = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
3804 elif field == "watcher_pause":
3805 entry = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
3807 raise errors.ParameterError(field)
3808 values.append(entry)
3812 class LUActivateInstanceDisks(NoHooksLU):
3813 """Bring up an instance's disks.
3816 _OP_REQP = ["instance_name"]
3819 def ExpandNames(self):
3820 self._ExpandAndLockInstance()
3821 self.needed_locks[locking.LEVEL_NODE] = []
3822 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
3824 def DeclareLocks(self, level):
3825 if level == locking.LEVEL_NODE:
3826 self._LockInstancesNodes()
3828 def CheckPrereq(self):
3829 """Check prerequisites.
3831 This checks that the instance is in the cluster.
3834 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3835 assert self.instance is not None, \
3836 "Cannot retrieve locked instance %s" % self.op.instance_name
3837 _CheckNodeOnline(self, self.instance.primary_node)
3838 if not hasattr(self.op, "ignore_size"):
3839 self.op.ignore_size = False
3841 def Exec(self, feedback_fn):
3842 """Activate the disks.
3845 disks_ok, disks_info = \
3846 _AssembleInstanceDisks(self, self.instance,
3847 ignore_size=self.op.ignore_size)
3849 raise errors.OpExecError("Cannot activate block devices")
3854 def _AssembleInstanceDisks(lu, instance, ignore_secondaries=False,
3856 """Prepare the block devices for an instance.
3858 This sets up the block devices on all nodes.
3860 @type lu: L{LogicalUnit}
3861 @param lu: the logical unit on whose behalf we execute
3862 @type instance: L{objects.Instance}
3863 @param instance: the instance for whose disks we assemble
3864 @type ignore_secondaries: boolean
3865 @param ignore_secondaries: if true, errors on secondary nodes
3866 won't result in an error return from the function
3867 @type ignore_size: boolean
3868 @param ignore_size: if true, the current known size of the disk
3869 will not be used during the disk activation, useful for cases
3870 when the size is wrong
3871 @return: False if the operation failed, otherwise a list of
3872 (host, instance_visible_name, node_visible_name)
3873 with the mapping from node devices to instance devices
3878 iname = instance.name
3879 # With the two passes mechanism we try to reduce the window of
3880 # opportunity for the race condition of switching DRBD to primary
3881 # before handshaking occured, but we do not eliminate it
3883 # The proper fix would be to wait (with some limits) until the
3884 # connection has been made and drbd transitions from WFConnection
3885 # into any other network-connected state (Connected, SyncTarget,
3888 # 1st pass, assemble on all nodes in secondary mode
3889 for inst_disk in instance.disks:
3890 for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
3892 node_disk = node_disk.Copy()
3893 node_disk.UnsetSize()
3894 lu.cfg.SetDiskID(node_disk, node)
3895 result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, False)
3896 msg = result.fail_msg
3898 lu.proc.LogWarning("Could not prepare block device %s on node %s"
3899 " (is_primary=False, pass=1): %s",
3900 inst_disk.iv_name, node, msg)
3901 if not ignore_secondaries:
3904 # FIXME: race condition on drbd migration to primary
3906 # 2nd pass, do only the primary node
3907 for inst_disk in instance.disks:
3910 for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
3911 if node != instance.primary_node:
3914 node_disk = node_disk.Copy()
3915 node_disk.UnsetSize()
3916 lu.cfg.SetDiskID(node_disk, node)
3917 result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, True)
3918 msg = result.fail_msg
3920 lu.proc.LogWarning("Could not prepare block device %s on node %s"
3921 " (is_primary=True, pass=2): %s",
3922 inst_disk.iv_name, node, msg)
3925 dev_path = result.payload
3927 device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
3929 # leave the disks configured for the primary node
3930 # this is a workaround that would be fixed better by
3931 # improving the logical/physical id handling
3932 for disk in instance.disks:
3933 lu.cfg.SetDiskID(disk, instance.primary_node)
3935 return disks_ok, device_info
3938 def _StartInstanceDisks(lu, instance, force):
3939 """Start the disks of an instance.
3942 disks_ok, _ = _AssembleInstanceDisks(lu, instance,
3943 ignore_secondaries=force)
3945 _ShutdownInstanceDisks(lu, instance)
3946 if force is not None and not force:
3947 lu.proc.LogWarning("", hint="If the message above refers to a"
3949 " you can retry the operation using '--force'.")
3950 raise errors.OpExecError("Disk consistency error")
3953 class LUDeactivateInstanceDisks(NoHooksLU):
3954 """Shutdown an instance's disks.
3957 _OP_REQP = ["instance_name"]
3960 def ExpandNames(self):
3961 self._ExpandAndLockInstance()
3962 self.needed_locks[locking.LEVEL_NODE] = []
3963 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
3965 def DeclareLocks(self, level):
3966 if level == locking.LEVEL_NODE:
3967 self._LockInstancesNodes()
3969 def CheckPrereq(self):
3970 """Check prerequisites.
3972 This checks that the instance is in the cluster.
3975 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3976 assert self.instance is not None, \
3977 "Cannot retrieve locked instance %s" % self.op.instance_name
3979 def Exec(self, feedback_fn):
3980 """Deactivate the disks
3983 instance = self.instance
3984 _SafeShutdownInstanceDisks(self, instance)
3987 def _SafeShutdownInstanceDisks(lu, instance):
3988 """Shutdown block devices of an instance.
3990 This function checks if an instance is running, before calling
3991 _ShutdownInstanceDisks.
3994 _CheckInstanceDown(lu, instance, "cannot shutdown disks")
3995 _ShutdownInstanceDisks(lu, instance)
3998 def _ShutdownInstanceDisks(lu, instance, ignore_primary=False):
3999 """Shutdown block devices of an instance.
4001 This does the shutdown on all nodes of the instance.
4003 If the ignore_primary is false, errors on the primary node are
4008 for disk in instance.disks:
4009 for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
4010 lu.cfg.SetDiskID(top_disk, node)
4011 result = lu.rpc.call_blockdev_shutdown(node, top_disk)
4012 msg = result.fail_msg
4014 lu.LogWarning("Could not shutdown block device %s on node %s: %s",
4015 disk.iv_name, node, msg)
4016 if not ignore_primary or node != instance.primary_node:
4021 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
4022 """Checks if a node has enough free memory.
4024 This function check if a given node has the needed amount of free
4025 memory. In case the node has less memory or we cannot get the
4026 information from the node, this function raise an OpPrereqError
4029 @type lu: C{LogicalUnit}
4030 @param lu: a logical unit from which we get configuration data
4032 @param node: the node to check
4033 @type reason: C{str}
4034 @param reason: string to use in the error message
4035 @type requested: C{int}
4036 @param requested: the amount of memory in MiB to check for
4037 @type hypervisor_name: C{str}
4038 @param hypervisor_name: the hypervisor to ask for memory stats
4039 @raise errors.OpPrereqError: if the node doesn't have enough memory, or
4040 we cannot check the node
4043 nodeinfo = lu.rpc.call_node_info([node], lu.cfg.GetVGName(), hypervisor_name)
4044 nodeinfo[node].Raise("Can't get data from node %s" % node,
4045 prereq=True, ecode=errors.ECODE_ENVIRON)
4046 free_mem = nodeinfo[node].payload.get('memory_free', None)
4047 if not isinstance(free_mem, int):
4048 raise errors.OpPrereqError("Can't compute free memory on node %s, result"
4049 " was '%s'" % (node, free_mem),
4050 errors.ECODE_ENVIRON)
4051 if requested > free_mem:
4052 raise errors.OpPrereqError("Not enough memory on node %s for %s:"
4053 " needed %s MiB, available %s MiB" %
4054 (node, reason, requested, free_mem),
4058 def _CheckNodesFreeDisk(lu, nodenames, requested):
4059 """Checks if nodes have enough free disk space in the default VG.
4061 This function check if all given nodes have the needed amount of
4062 free disk. In case any node has less disk or we cannot get the
4063 information from the node, this function raise an OpPrereqError
4066 @type lu: C{LogicalUnit}
4067 @param lu: a logical unit from which we get configuration data
4068 @type nodenames: C{list}
4069 @param nodenames: the list of node names to check
4070 @type requested: C{int}
4071 @param requested: the amount of disk in MiB to check for
4072 @raise errors.OpPrereqError: if the node doesn't have enough disk, or
4073 we cannot check the node
4076 nodeinfo = lu.rpc.call_node_info(nodenames, lu.cfg.GetVGName(),
4077 lu.cfg.GetHypervisorType())
4078 for node in nodenames:
4079 info = nodeinfo[node]
4080 info.Raise("Cannot get current information from node %s" % node,
4081 prereq=True, ecode=errors.ECODE_ENVIRON)
4082 vg_free = info.payload.get("vg_free", None)
4083 if not isinstance(vg_free, int):
4084 raise errors.OpPrereqError("Can't compute free disk space on node %s,"
4085 " result was '%s'" % (node, vg_free),
4086 errors.ECODE_ENVIRON)
4087 if requested > vg_free:
4088 raise errors.OpPrereqError("Not enough disk space on target node %s:"
4089 " required %d MiB, available %d MiB" %
4090 (node, requested, vg_free),
4094 class LUStartupInstance(LogicalUnit):
4095 """Starts an instance.
4098 HPATH = "instance-start"
4099 HTYPE = constants.HTYPE_INSTANCE
4100 _OP_REQP = ["instance_name", "force"]
4103 def ExpandNames(self):
4104 self._ExpandAndLockInstance()
4106 def BuildHooksEnv(self):
4109 This runs on master, primary and secondary nodes of the instance.
4113 "FORCE": self.op.force,
4115 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
4116 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4119 def CheckPrereq(self):
4120 """Check prerequisites.
4122 This checks that the instance is in the cluster.
4125 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4126 assert self.instance is not None, \
4127 "Cannot retrieve locked instance %s" % self.op.instance_name
4130 self.beparams = getattr(self.op, "beparams", {})
4132 if not isinstance(self.beparams, dict):
4133 raise errors.OpPrereqError("Invalid beparams passed: %s, expected"
4134 " dict" % (type(self.beparams), ),
4136 # fill the beparams dict
4137 utils.ForceDictType(self.beparams, constants.BES_PARAMETER_TYPES)
4138 self.op.beparams = self.beparams
4141 self.hvparams = getattr(self.op, "hvparams", {})
4143 if not isinstance(self.hvparams, dict):
4144 raise errors.OpPrereqError("Invalid hvparams passed: %s, expected"
4145 " dict" % (type(self.hvparams), ),
4148 # check hypervisor parameter syntax (locally)
4149 cluster = self.cfg.GetClusterInfo()
4150 utils.ForceDictType(self.hvparams, constants.HVS_PARAMETER_TYPES)
4151 filled_hvp = objects.FillDict(cluster.hvparams[instance.hypervisor],
4153 filled_hvp.update(self.hvparams)
4154 hv_type = hypervisor.GetHypervisor(instance.hypervisor)
4155 hv_type.CheckParameterSyntax(filled_hvp)
4156 _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
4157 self.op.hvparams = self.hvparams
4159 _CheckNodeOnline(self, instance.primary_node)
4161 bep = self.cfg.GetClusterInfo().FillBE(instance)
4162 # check bridges existence
4163 _CheckInstanceBridgesExist(self, instance)
4165 remote_info = self.rpc.call_instance_info(instance.primary_node,
4167 instance.hypervisor)
4168 remote_info.Raise("Error checking node %s" % instance.primary_node,
4169 prereq=True, ecode=errors.ECODE_ENVIRON)
4170 if not remote_info.payload: # not running already
4171 _CheckNodeFreeMemory(self, instance.primary_node,
4172 "starting instance %s" % instance.name,
4173 bep[constants.BE_MEMORY], instance.hypervisor)
4175 def Exec(self, feedback_fn):
4176 """Start the instance.
4179 instance = self.instance
4180 force = self.op.force
4182 self.cfg.MarkInstanceUp(instance.name)
4184 node_current = instance.primary_node
4186 _StartInstanceDisks(self, instance, force)
4188 result = self.rpc.call_instance_start(node_current, instance,
4189 self.hvparams, self.beparams)
4190 msg = result.fail_msg
4192 _ShutdownInstanceDisks(self, instance)
4193 raise errors.OpExecError("Could not start instance: %s" % msg)
4196 class LURebootInstance(LogicalUnit):
4197 """Reboot an instance.
4200 HPATH = "instance-reboot"
4201 HTYPE = constants.HTYPE_INSTANCE
4202 _OP_REQP = ["instance_name", "ignore_secondaries", "reboot_type"]
4205 def CheckArguments(self):
4206 """Check the arguments.
4209 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
4210 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4212 def ExpandNames(self):
4213 if self.op.reboot_type not in [constants.INSTANCE_REBOOT_SOFT,
4214 constants.INSTANCE_REBOOT_HARD,
4215 constants.INSTANCE_REBOOT_FULL]:
4216 raise errors.ParameterError("reboot type not in [%s, %s, %s]" %
4217 (constants.INSTANCE_REBOOT_SOFT,
4218 constants.INSTANCE_REBOOT_HARD,
4219 constants.INSTANCE_REBOOT_FULL))
4220 self._ExpandAndLockInstance()
4222 def BuildHooksEnv(self):
4225 This runs on master, primary and secondary nodes of the instance.
4229 "IGNORE_SECONDARIES": self.op.ignore_secondaries,
4230 "REBOOT_TYPE": self.op.reboot_type,
4231 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
4233 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
4234 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4237 def CheckPrereq(self):
4238 """Check prerequisites.
4240 This checks that the instance is in the cluster.
4243 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4244 assert self.instance is not None, \
4245 "Cannot retrieve locked instance %s" % self.op.instance_name
4247 _CheckNodeOnline(self, instance.primary_node)
4249 # check bridges existence
4250 _CheckInstanceBridgesExist(self, instance)
4252 def Exec(self, feedback_fn):
4253 """Reboot the instance.
4256 instance = self.instance
4257 ignore_secondaries = self.op.ignore_secondaries
4258 reboot_type = self.op.reboot_type
4260 node_current = instance.primary_node
4262 if reboot_type in [constants.INSTANCE_REBOOT_SOFT,
4263 constants.INSTANCE_REBOOT_HARD]:
4264 for disk in instance.disks:
4265 self.cfg.SetDiskID(disk, node_current)
4266 result = self.rpc.call_instance_reboot(node_current, instance,
4268 self.shutdown_timeout)
4269 result.Raise("Could not reboot instance")
4271 result = self.rpc.call_instance_shutdown(node_current, instance,
4272 self.shutdown_timeout)
4273 result.Raise("Could not shutdown instance for full reboot")
4274 _ShutdownInstanceDisks(self, instance)
4275 _StartInstanceDisks(self, instance, ignore_secondaries)
4276 result = self.rpc.call_instance_start(node_current, instance, None, None)
4277 msg = result.fail_msg
4279 _ShutdownInstanceDisks(self, instance)
4280 raise errors.OpExecError("Could not start instance for"
4281 " full reboot: %s" % msg)
4283 self.cfg.MarkInstanceUp(instance.name)
4286 class LUShutdownInstance(LogicalUnit):
4287 """Shutdown an instance.
4290 HPATH = "instance-stop"
4291 HTYPE = constants.HTYPE_INSTANCE
4292 _OP_REQP = ["instance_name"]
4295 def CheckArguments(self):
4296 """Check the arguments.
4299 self.timeout = getattr(self.op, "timeout",
4300 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4302 def ExpandNames(self):
4303 self._ExpandAndLockInstance()
4305 def BuildHooksEnv(self):
4308 This runs on master, primary and secondary nodes of the instance.
4311 env = _BuildInstanceHookEnvByObject(self, self.instance)
4312 env["TIMEOUT"] = self.timeout
4313 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4316 def CheckPrereq(self):
4317 """Check prerequisites.
4319 This checks that the instance is in the cluster.
4322 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4323 assert self.instance is not None, \
4324 "Cannot retrieve locked instance %s" % self.op.instance_name
4325 _CheckNodeOnline(self, self.instance.primary_node)
4327 def Exec(self, feedback_fn):
4328 """Shutdown the instance.
4331 instance = self.instance
4332 node_current = instance.primary_node
4333 timeout = self.timeout
4334 self.cfg.MarkInstanceDown(instance.name)
4335 result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
4336 msg = result.fail_msg
4338 self.proc.LogWarning("Could not shutdown instance: %s" % msg)
4340 _ShutdownInstanceDisks(self, instance)
4343 class LUReinstallInstance(LogicalUnit):
4344 """Reinstall an instance.
4347 HPATH = "instance-reinstall"
4348 HTYPE = constants.HTYPE_INSTANCE
4349 _OP_REQP = ["instance_name"]
4352 def ExpandNames(self):
4353 self._ExpandAndLockInstance()
4355 def BuildHooksEnv(self):
4358 This runs on master, primary and secondary nodes of the instance.
4361 env = _BuildInstanceHookEnvByObject(self, self.instance)
4362 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4365 def CheckPrereq(self):
4366 """Check prerequisites.
4368 This checks that the instance is in the cluster and is not running.
4371 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4372 assert instance is not None, \
4373 "Cannot retrieve locked instance %s" % self.op.instance_name
4374 _CheckNodeOnline(self, instance.primary_node)
4376 if instance.disk_template == constants.DT_DISKLESS:
4377 raise errors.OpPrereqError("Instance '%s' has no disks" %
4378 self.op.instance_name,
4380 _CheckInstanceDown(self, instance, "cannot reinstall")
4382 self.op.os_type = getattr(self.op, "os_type", None)
4383 self.op.force_variant = getattr(self.op, "force_variant", False)
4384 if self.op.os_type is not None:
4386 pnode = _ExpandNodeName(self.cfg, instance.primary_node)
4387 _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
4389 self.instance = instance
4391 def Exec(self, feedback_fn):
4392 """Reinstall the instance.
4395 inst = self.instance
4397 if self.op.os_type is not None:
4398 feedback_fn("Changing OS to '%s'..." % self.op.os_type)
4399 inst.os = self.op.os_type
4400 self.cfg.Update(inst, feedback_fn)
4402 _StartInstanceDisks(self, inst, None)
4404 feedback_fn("Running the instance OS create scripts...")
4405 # FIXME: pass debug option from opcode to backend
4406 result = self.rpc.call_instance_os_add(inst.primary_node, inst, True,
4407 self.op.debug_level)
4408 result.Raise("Could not install OS for instance %s on node %s" %
4409 (inst.name, inst.primary_node))
4411 _ShutdownInstanceDisks(self, inst)
4414 class LURecreateInstanceDisks(LogicalUnit):
4415 """Recreate an instance's missing disks.
4418 HPATH = "instance-recreate-disks"
4419 HTYPE = constants.HTYPE_INSTANCE
4420 _OP_REQP = ["instance_name", "disks"]
4423 def CheckArguments(self):
4424 """Check the arguments.
4427 if not isinstance(self.op.disks, list):
4428 raise errors.OpPrereqError("Invalid disks parameter", errors.ECODE_INVAL)
4429 for item in self.op.disks:
4430 if (not isinstance(item, int) or
4432 raise errors.OpPrereqError("Invalid disk specification '%s'" %
4433 str(item), errors.ECODE_INVAL)
4435 def ExpandNames(self):
4436 self._ExpandAndLockInstance()
4438 def BuildHooksEnv(self):
4441 This runs on master, primary and secondary nodes of the instance.
4444 env = _BuildInstanceHookEnvByObject(self, self.instance)
4445 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4448 def CheckPrereq(self):
4449 """Check prerequisites.
4451 This checks that the instance is in the cluster and is not running.
4454 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4455 assert instance is not None, \
4456 "Cannot retrieve locked instance %s" % self.op.instance_name
4457 _CheckNodeOnline(self, instance.primary_node)
4459 if instance.disk_template == constants.DT_DISKLESS:
4460 raise errors.OpPrereqError("Instance '%s' has no disks" %
4461 self.op.instance_name, errors.ECODE_INVAL)
4462 _CheckInstanceDown(self, instance, "cannot recreate disks")
4464 if not self.op.disks:
4465 self.op.disks = range(len(instance.disks))
4467 for idx in self.op.disks:
4468 if idx >= len(instance.disks):
4469 raise errors.OpPrereqError("Invalid disk index passed '%s'" % idx,
4472 self.instance = instance
4474 def Exec(self, feedback_fn):
4475 """Recreate the disks.
4479 for idx, _ in enumerate(self.instance.disks):
4480 if idx not in self.op.disks: # disk idx has not been passed in
4484 _CreateDisks(self, self.instance, to_skip=to_skip)
4487 class LURenameInstance(LogicalUnit):
4488 """Rename an instance.
4491 HPATH = "instance-rename"
4492 HTYPE = constants.HTYPE_INSTANCE
4493 _OP_REQP = ["instance_name", "new_name"]
4495 def BuildHooksEnv(self):
4498 This runs on master, primary and secondary nodes of the instance.
4501 env = _BuildInstanceHookEnvByObject(self, self.instance)
4502 env["INSTANCE_NEW_NAME"] = self.op.new_name
4503 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
4506 def CheckPrereq(self):
4507 """Check prerequisites.
4509 This checks that the instance is in the cluster and is not running.
4512 self.op.instance_name = _ExpandInstanceName(self.cfg,
4513 self.op.instance_name)
4514 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4515 assert instance is not None
4516 _CheckNodeOnline(self, instance.primary_node)
4517 _CheckInstanceDown(self, instance, "cannot rename")
4518 self.instance = instance
4520 # new name verification
4521 name_info = utils.GetHostInfo(self.op.new_name)
4523 self.op.new_name = new_name = name_info.name
4524 instance_list = self.cfg.GetInstanceList()
4525 if new_name in instance_list:
4526 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
4527 new_name, errors.ECODE_EXISTS)
4529 if not getattr(self.op, "ignore_ip", False):
4530 if utils.TcpPing(name_info.ip, constants.DEFAULT_NODED_PORT):
4531 raise errors.OpPrereqError("IP %s of instance %s already in use" %
4532 (name_info.ip, new_name),
4533 errors.ECODE_NOTUNIQUE)
4536 def Exec(self, feedback_fn):
4537 """Reinstall the instance.
4540 inst = self.instance
4541 old_name = inst.name
4543 if inst.disk_template == constants.DT_FILE:
4544 old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
4546 self.cfg.RenameInstance(inst.name, self.op.new_name)
4547 # Change the instance lock. This is definitely safe while we hold the BGL
4548 self.context.glm.remove(locking.LEVEL_INSTANCE, old_name)
4549 self.context.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
4551 # re-read the instance from the configuration after rename
4552 inst = self.cfg.GetInstanceInfo(self.op.new_name)
4554 if inst.disk_template == constants.DT_FILE:
4555 new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
4556 result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
4557 old_file_storage_dir,
4558 new_file_storage_dir)
4559 result.Raise("Could not rename on node %s directory '%s' to '%s'"
4560 " (but the instance has been renamed in Ganeti)" %
4561 (inst.primary_node, old_file_storage_dir,
4562 new_file_storage_dir))
4564 _StartInstanceDisks(self, inst, None)
4566 result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
4567 old_name, self.op.debug_level)
4568 msg = result.fail_msg
4570 msg = ("Could not run OS rename script for instance %s on node %s"
4571 " (but the instance has been renamed in Ganeti): %s" %
4572 (inst.name, inst.primary_node, msg))
4573 self.proc.LogWarning(msg)
4575 _ShutdownInstanceDisks(self, inst)
4578 class LURemoveInstance(LogicalUnit):
4579 """Remove an instance.
4582 HPATH = "instance-remove"
4583 HTYPE = constants.HTYPE_INSTANCE
4584 _OP_REQP = ["instance_name", "ignore_failures"]
4587 def CheckArguments(self):
4588 """Check the arguments.
4591 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
4592 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4594 def ExpandNames(self):
4595 self._ExpandAndLockInstance()
4596 self.needed_locks[locking.LEVEL_NODE] = []
4597 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4599 def DeclareLocks(self, level):
4600 if level == locking.LEVEL_NODE:
4601 self._LockInstancesNodes()
4603 def BuildHooksEnv(self):
4606 This runs on master, primary and secondary nodes of the instance.
4609 env = _BuildInstanceHookEnvByObject(self, self.instance)
4610 env["SHUTDOWN_TIMEOUT"] = self.shutdown_timeout
4611 nl = [self.cfg.GetMasterNode()]
4612 nl_post = list(self.instance.all_nodes) + nl
4613 return env, nl, nl_post
4615 def CheckPrereq(self):
4616 """Check prerequisites.
4618 This checks that the instance is in the cluster.
4621 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4622 assert self.instance is not None, \
4623 "Cannot retrieve locked instance %s" % self.op.instance_name
4625 def Exec(self, feedback_fn):
4626 """Remove the instance.
4629 instance = self.instance
4630 logging.info("Shutting down instance %s on node %s",
4631 instance.name, instance.primary_node)
4633 result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
4634 self.shutdown_timeout)
4635 msg = result.fail_msg
4637 if self.op.ignore_failures:
4638 feedback_fn("Warning: can't shutdown instance: %s" % msg)
4640 raise errors.OpExecError("Could not shutdown instance %s on"
4642 (instance.name, instance.primary_node, msg))
4644 _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
4647 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
4648 """Utility function to remove an instance.
4651 logging.info("Removing block devices for instance %s", instance.name)
4653 if not _RemoveDisks(lu, instance):
4654 if not ignore_failures:
4655 raise errors.OpExecError("Can't remove instance's disks")
4656 feedback_fn("Warning: can't remove instance's disks")
4658 logging.info("Removing instance %s out of cluster config", instance.name)
4660 lu.cfg.RemoveInstance(instance.name)
4662 assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
4663 "Instance lock removal conflict"
4665 # Remove lock for the instance
4666 lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
4669 class LUQueryInstances(NoHooksLU):
4670 """Logical unit for querying instances.
4673 # pylint: disable-msg=W0142
4674 _OP_REQP = ["output_fields", "names", "use_locking"]
4676 _SIMPLE_FIELDS = ["name", "os", "network_port", "hypervisor",
4677 "serial_no", "ctime", "mtime", "uuid"]
4678 _FIELDS_STATIC = utils.FieldSet(*["name", "os", "pnode", "snodes",
4680 "disk_template", "ip", "mac", "bridge",
4681 "nic_mode", "nic_link",
4682 "sda_size", "sdb_size", "vcpus", "tags",
4683 "network_port", "beparams",
4684 r"(disk)\.(size)/([0-9]+)",
4685 r"(disk)\.(sizes)", "disk_usage",
4686 r"(nic)\.(mac|ip|mode|link)/([0-9]+)",
4687 r"(nic)\.(bridge)/([0-9]+)",
4688 r"(nic)\.(macs|ips|modes|links|bridges)",
4689 r"(disk|nic)\.(count)",
4691 ] + _SIMPLE_FIELDS +
4693 for name in constants.HVS_PARAMETERS
4694 if name not in constants.HVC_GLOBALS] +
4696 for name in constants.BES_PARAMETERS])
4697 _FIELDS_DYNAMIC = utils.FieldSet("oper_state", "oper_ram", "status")
4700 def ExpandNames(self):
4701 _CheckOutputFields(static=self._FIELDS_STATIC,
4702 dynamic=self._FIELDS_DYNAMIC,
4703 selected=self.op.output_fields)
4705 self.needed_locks = {}
4706 self.share_locks[locking.LEVEL_INSTANCE] = 1
4707 self.share_locks[locking.LEVEL_NODE] = 1
4710 self.wanted = _GetWantedInstances(self, self.op.names)
4712 self.wanted = locking.ALL_SET
4714 self.do_node_query = self._FIELDS_STATIC.NonMatching(self.op.output_fields)
4715 self.do_locking = self.do_node_query and self.op.use_locking
4717 self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
4718 self.needed_locks[locking.LEVEL_NODE] = []
4719 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4721 def DeclareLocks(self, level):
4722 if level == locking.LEVEL_NODE and self.do_locking:
4723 self._LockInstancesNodes()
4725 def CheckPrereq(self):
4726 """Check prerequisites.
4731 def Exec(self, feedback_fn):
4732 """Computes the list of nodes and their attributes.
4735 # pylint: disable-msg=R0912
4736 # way too many branches here
4737 all_info = self.cfg.GetAllInstancesInfo()
4738 if self.wanted == locking.ALL_SET:
4739 # caller didn't specify instance names, so ordering is not important
4741 instance_names = self.acquired_locks[locking.LEVEL_INSTANCE]
4743 instance_names = all_info.keys()
4744 instance_names = utils.NiceSort(instance_names)
4746 # caller did specify names, so we must keep the ordering
4748 tgt_set = self.acquired_locks[locking.LEVEL_INSTANCE]
4750 tgt_set = all_info.keys()
4751 missing = set(self.wanted).difference(tgt_set)
4753 raise errors.OpExecError("Some instances were removed before"
4754 " retrieving their data: %s" % missing)
4755 instance_names = self.wanted
4757 instance_list = [all_info[iname] for iname in instance_names]
4759 # begin data gathering
4761 nodes = frozenset([inst.primary_node for inst in instance_list])
4762 hv_list = list(set([inst.hypervisor for inst in instance_list]))
4766 if self.do_node_query:
4768 node_data = self.rpc.call_all_instances_info(nodes, hv_list)
4770 result = node_data[name]
4772 # offline nodes will be in both lists
4773 off_nodes.append(name)
4775 bad_nodes.append(name)
4778 live_data.update(result.payload)
4779 # else no instance is alive
4781 live_data = dict([(name, {}) for name in instance_names])
4783 # end data gathering
4788 cluster = self.cfg.GetClusterInfo()
4789 for instance in instance_list:
4791 i_hv = cluster.FillHV(instance, skip_globals=True)
4792 i_be = cluster.FillBE(instance)
4793 i_nicp = [objects.FillDict(cluster.nicparams[constants.PP_DEFAULT],
4794 nic.nicparams) for nic in instance.nics]
4795 for field in self.op.output_fields:
4796 st_match = self._FIELDS_STATIC.Matches(field)
4797 if field in self._SIMPLE_FIELDS:
4798 val = getattr(instance, field)
4799 elif field == "pnode":
4800 val = instance.primary_node
4801 elif field == "snodes":
4802 val = list(instance.secondary_nodes)
4803 elif field == "admin_state":
4804 val = instance.admin_up
4805 elif field == "oper_state":
4806 if instance.primary_node in bad_nodes:
4809 val = bool(live_data.get(instance.name))
4810 elif field == "status":
4811 if instance.primary_node in off_nodes:
4812 val = "ERROR_nodeoffline"
4813 elif instance.primary_node in bad_nodes:
4814 val = "ERROR_nodedown"
4816 running = bool(live_data.get(instance.name))
4818 if instance.admin_up:
4823 if instance.admin_up:
4827 elif field == "oper_ram":
4828 if instance.primary_node in bad_nodes:
4830 elif instance.name in live_data:
4831 val = live_data[instance.name].get("memory", "?")
4834 elif field == "vcpus":
4835 val = i_be[constants.BE_VCPUS]
4836 elif field == "disk_template":
4837 val = instance.disk_template
4840 val = instance.nics[0].ip
4843 elif field == "nic_mode":
4845 val = i_nicp[0][constants.NIC_MODE]
4848 elif field == "nic_link":
4850 val = i_nicp[0][constants.NIC_LINK]
4853 elif field == "bridge":
4854 if (instance.nics and
4855 i_nicp[0][constants.NIC_MODE] == constants.NIC_MODE_BRIDGED):
4856 val = i_nicp[0][constants.NIC_LINK]
4859 elif field == "mac":
4861 val = instance.nics[0].mac
4864 elif field == "sda_size" or field == "sdb_size":
4865 idx = ord(field[2]) - ord('a')
4867 val = instance.FindDisk(idx).size
4868 except errors.OpPrereqError:
4870 elif field == "disk_usage": # total disk usage per node
4871 disk_sizes = [{'size': disk.size} for disk in instance.disks]
4872 val = _ComputeDiskSize(instance.disk_template, disk_sizes)
4873 elif field == "tags":
4874 val = list(instance.GetTags())
4875 elif field == "hvparams":
4877 elif (field.startswith(HVPREFIX) and
4878 field[len(HVPREFIX):] in constants.HVS_PARAMETERS and
4879 field[len(HVPREFIX):] not in constants.HVC_GLOBALS):
4880 val = i_hv.get(field[len(HVPREFIX):], None)
4881 elif field == "beparams":
4883 elif (field.startswith(BEPREFIX) and
4884 field[len(BEPREFIX):] in constants.BES_PARAMETERS):
4885 val = i_be.get(field[len(BEPREFIX):], None)
4886 elif st_match and st_match.groups():
4887 # matches a variable list
4888 st_groups = st_match.groups()
4889 if st_groups and st_groups[0] == "disk":
4890 if st_groups[1] == "count":
4891 val = len(instance.disks)
4892 elif st_groups[1] == "sizes":
4893 val = [disk.size for disk in instance.disks]
4894 elif st_groups[1] == "size":
4896 val = instance.FindDisk(st_groups[2]).size
4897 except errors.OpPrereqError:
4900 assert False, "Unhandled disk parameter"
4901 elif st_groups[0] == "nic":
4902 if st_groups[1] == "count":
4903 val = len(instance.nics)
4904 elif st_groups[1] == "macs":
4905 val = [nic.mac for nic in instance.nics]
4906 elif st_groups[1] == "ips":
4907 val = [nic.ip for nic in instance.nics]
4908 elif st_groups[1] == "modes":
4909 val = [nicp[constants.NIC_MODE] for nicp in i_nicp]
4910 elif st_groups[1] == "links":
4911 val = [nicp[constants.NIC_LINK] for nicp in i_nicp]
4912 elif st_groups[1] == "bridges":
4915 if nicp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
4916 val.append(nicp[constants.NIC_LINK])
4921 nic_idx = int(st_groups[2])
4922 if nic_idx >= len(instance.nics):
4925 if st_groups[1] == "mac":
4926 val = instance.nics[nic_idx].mac
4927 elif st_groups[1] == "ip":
4928 val = instance.nics[nic_idx].ip
4929 elif st_groups[1] == "mode":
4930 val = i_nicp[nic_idx][constants.NIC_MODE]
4931 elif st_groups[1] == "link":
4932 val = i_nicp[nic_idx][constants.NIC_LINK]
4933 elif st_groups[1] == "bridge":
4934 nic_mode = i_nicp[nic_idx][constants.NIC_MODE]
4935 if nic_mode == constants.NIC_MODE_BRIDGED:
4936 val = i_nicp[nic_idx][constants.NIC_LINK]
4940 assert False, "Unhandled NIC parameter"
4942 assert False, ("Declared but unhandled variable parameter '%s'" %
4945 assert False, "Declared but unhandled parameter '%s'" % field
4952 class LUFailoverInstance(LogicalUnit):
4953 """Failover an instance.
4956 HPATH = "instance-failover"
4957 HTYPE = constants.HTYPE_INSTANCE
4958 _OP_REQP = ["instance_name", "ignore_consistency"]
4961 def CheckArguments(self):
4962 """Check the arguments.
4965 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
4966 constants.DEFAULT_SHUTDOWN_TIMEOUT)
4968 def ExpandNames(self):
4969 self._ExpandAndLockInstance()
4970 self.needed_locks[locking.LEVEL_NODE] = []
4971 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4973 def DeclareLocks(self, level):
4974 if level == locking.LEVEL_NODE:
4975 self._LockInstancesNodes()
4977 def BuildHooksEnv(self):
4980 This runs on master, primary and secondary nodes of the instance.
4983 instance = self.instance
4984 source_node = instance.primary_node
4985 target_node = instance.secondary_nodes[0]
4987 "IGNORE_CONSISTENCY": self.op.ignore_consistency,
4988 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
4989 "OLD_PRIMARY": source_node,
4990 "OLD_SECONDARY": target_node,
4991 "NEW_PRIMARY": target_node,
4992 "NEW_SECONDARY": source_node,
4994 env.update(_BuildInstanceHookEnvByObject(self, instance))
4995 nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
4997 nl_post.append(source_node)
4998 return env, nl, nl_post
5000 def CheckPrereq(self):
5001 """Check prerequisites.
5003 This checks that the instance is in the cluster.
5006 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5007 assert self.instance is not None, \
5008 "Cannot retrieve locked instance %s" % self.op.instance_name
5010 bep = self.cfg.GetClusterInfo().FillBE(instance)
5011 if instance.disk_template not in constants.DTS_NET_MIRROR:
5012 raise errors.OpPrereqError("Instance's disk layout is not"
5013 " network mirrored, cannot failover.",
5016 secondary_nodes = instance.secondary_nodes
5017 if not secondary_nodes:
5018 raise errors.ProgrammerError("no secondary node but using "
5019 "a mirrored disk template")
5021 target_node = secondary_nodes[0]
5022 _CheckNodeOnline(self, target_node)
5023 _CheckNodeNotDrained(self, target_node)
5024 if instance.admin_up:
5025 # check memory requirements on the secondary node
5026 _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
5027 instance.name, bep[constants.BE_MEMORY],
5028 instance.hypervisor)
5030 self.LogInfo("Not checking memory on the secondary node as"
5031 " instance will not be started")
5033 # check bridge existance
5034 _CheckInstanceBridgesExist(self, instance, node=target_node)
5036 def Exec(self, feedback_fn):
5037 """Failover an instance.
5039 The failover is done by shutting it down on its present node and
5040 starting it on the secondary.
5043 instance = self.instance
5045 source_node = instance.primary_node
5046 target_node = instance.secondary_nodes[0]
5048 if instance.admin_up:
5049 feedback_fn("* checking disk consistency between source and target")
5050 for dev in instance.disks:
5051 # for drbd, these are drbd over lvm
5052 if not _CheckDiskConsistency(self, dev, target_node, False):
5053 if not self.op.ignore_consistency:
5054 raise errors.OpExecError("Disk %s is degraded on target node,"
5055 " aborting failover." % dev.iv_name)
5057 feedback_fn("* not checking disk consistency as instance is not running")
5059 feedback_fn("* shutting down instance on source node")
5060 logging.info("Shutting down instance %s on node %s",
5061 instance.name, source_node)
5063 result = self.rpc.call_instance_shutdown(source_node, instance,
5064 self.shutdown_timeout)
5065 msg = result.fail_msg
5067 if self.op.ignore_consistency:
5068 self.proc.LogWarning("Could not shutdown instance %s on node %s."
5069 " Proceeding anyway. Please make sure node"
5070 " %s is down. Error details: %s",
5071 instance.name, source_node, source_node, msg)
5073 raise errors.OpExecError("Could not shutdown instance %s on"
5075 (instance.name, source_node, msg))
5077 feedback_fn("* deactivating the instance's disks on source node")
5078 if not _ShutdownInstanceDisks(self, instance, ignore_primary=True):
5079 raise errors.OpExecError("Can't shut down the instance's disks.")
5081 instance.primary_node = target_node
5082 # distribute new instance config to the other nodes
5083 self.cfg.Update(instance, feedback_fn)
5085 # Only start the instance if it's marked as up
5086 if instance.admin_up:
5087 feedback_fn("* activating the instance's disks on target node")
5088 logging.info("Starting instance %s on node %s",
5089 instance.name, target_node)
5091 disks_ok, _ = _AssembleInstanceDisks(self, instance,
5092 ignore_secondaries=True)
5094 _ShutdownInstanceDisks(self, instance)
5095 raise errors.OpExecError("Can't activate the instance's disks")
5097 feedback_fn("* starting the instance on the target node")
5098 result = self.rpc.call_instance_start(target_node, instance, None, None)
5099 msg = result.fail_msg
5101 _ShutdownInstanceDisks(self, instance)
5102 raise errors.OpExecError("Could not start instance %s on node %s: %s" %
5103 (instance.name, target_node, msg))
5106 class LUMigrateInstance(LogicalUnit):
5107 """Migrate an instance.
5109 This is migration without shutting down, compared to the failover,
5110 which is done with shutdown.
5113 HPATH = "instance-migrate"
5114 HTYPE = constants.HTYPE_INSTANCE
5115 _OP_REQP = ["instance_name", "live", "cleanup"]
5119 def ExpandNames(self):
5120 self._ExpandAndLockInstance()
5122 self.needed_locks[locking.LEVEL_NODE] = []
5123 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5125 self._migrater = TLMigrateInstance(self, self.op.instance_name,
5126 self.op.live, self.op.cleanup)
5127 self.tasklets = [self._migrater]
5129 def DeclareLocks(self, level):
5130 if level == locking.LEVEL_NODE:
5131 self._LockInstancesNodes()
5133 def BuildHooksEnv(self):
5136 This runs on master, primary and secondary nodes of the instance.
5139 instance = self._migrater.instance
5140 source_node = instance.primary_node
5141 target_node = instance.secondary_nodes[0]
5142 env = _BuildInstanceHookEnvByObject(self, instance)
5143 env["MIGRATE_LIVE"] = self.op.live
5144 env["MIGRATE_CLEANUP"] = self.op.cleanup
5146 "OLD_PRIMARY": source_node,
5147 "OLD_SECONDARY": target_node,
5148 "NEW_PRIMARY": target_node,
5149 "NEW_SECONDARY": source_node,
5151 nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
5153 nl_post.append(source_node)
5154 return env, nl, nl_post
5157 class LUMoveInstance(LogicalUnit):
5158 """Move an instance by data-copying.
5161 HPATH = "instance-move"
5162 HTYPE = constants.HTYPE_INSTANCE
5163 _OP_REQP = ["instance_name", "target_node"]
5166 def CheckArguments(self):
5167 """Check the arguments.
5170 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
5171 constants.DEFAULT_SHUTDOWN_TIMEOUT)
5173 def ExpandNames(self):
5174 self._ExpandAndLockInstance()
5175 target_node = _ExpandNodeName(self.cfg, self.op.target_node)
5176 self.op.target_node = target_node
5177 self.needed_locks[locking.LEVEL_NODE] = [target_node]
5178 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
5180 def DeclareLocks(self, level):
5181 if level == locking.LEVEL_NODE:
5182 self._LockInstancesNodes(primary_only=True)
5184 def BuildHooksEnv(self):
5187 This runs on master, primary and secondary nodes of the instance.
5191 "TARGET_NODE": self.op.target_node,
5192 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
5194 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
5195 nl = [self.cfg.GetMasterNode()] + [self.instance.primary_node,
5196 self.op.target_node]
5199 def CheckPrereq(self):
5200 """Check prerequisites.
5202 This checks that the instance is in the cluster.
5205 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5206 assert self.instance is not None, \
5207 "Cannot retrieve locked instance %s" % self.op.instance_name
5209 node = self.cfg.GetNodeInfo(self.op.target_node)
5210 assert node is not None, \
5211 "Cannot retrieve locked node %s" % self.op.target_node
5213 self.target_node = target_node = node.name
5215 if target_node == instance.primary_node:
5216 raise errors.OpPrereqError("Instance %s is already on the node %s" %
5217 (instance.name, target_node),
5220 bep = self.cfg.GetClusterInfo().FillBE(instance)
5222 for idx, dsk in enumerate(instance.disks):
5223 if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
5224 raise errors.OpPrereqError("Instance disk %d has a complex layout,"
5225 " cannot copy" % idx, errors.ECODE_STATE)
5227 _CheckNodeOnline(self, target_node)
5228 _CheckNodeNotDrained(self, target_node)
5230 if instance.admin_up:
5231 # check memory requirements on the secondary node
5232 _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
5233 instance.name, bep[constants.BE_MEMORY],
5234 instance.hypervisor)
5236 self.LogInfo("Not checking memory on the secondary node as"
5237 " instance will not be started")
5239 # check bridge existance
5240 _CheckInstanceBridgesExist(self, instance, node=target_node)
5242 def Exec(self, feedback_fn):
5243 """Move an instance.
5245 The move is done by shutting it down on its present node, copying
5246 the data over (slow) and starting it on the new node.
5249 instance = self.instance
5251 source_node = instance.primary_node
5252 target_node = self.target_node
5254 self.LogInfo("Shutting down instance %s on source node %s",
5255 instance.name, source_node)
5257 result = self.rpc.call_instance_shutdown(source_node, instance,
5258 self.shutdown_timeout)
5259 msg = result.fail_msg
5261 if self.op.ignore_consistency:
5262 self.proc.LogWarning("Could not shutdown instance %s on node %s."
5263 " Proceeding anyway. Please make sure node"
5264 " %s is down. Error details: %s",
5265 instance.name, source_node, source_node, msg)
5267 raise errors.OpExecError("Could not shutdown instance %s on"
5269 (instance.name, source_node, msg))
5271 # create the target disks
5273 _CreateDisks(self, instance, target_node=target_node)
5274 except errors.OpExecError:
5275 self.LogWarning("Device creation failed, reverting...")
5277 _RemoveDisks(self, instance, target_node=target_node)
5279 self.cfg.ReleaseDRBDMinors(instance.name)
5282 cluster_name = self.cfg.GetClusterInfo().cluster_name
5285 # activate, get path, copy the data over
5286 for idx, disk in enumerate(instance.disks):
5287 self.LogInfo("Copying data for disk %d", idx)
5288 result = self.rpc.call_blockdev_assemble(target_node, disk,
5289 instance.name, True)
5291 self.LogWarning("Can't assemble newly created disk %d: %s",
5292 idx, result.fail_msg)
5293 errs.append(result.fail_msg)
5295 dev_path = result.payload
5296 result = self.rpc.call_blockdev_export(source_node, disk,
5297 target_node, dev_path,
5300 self.LogWarning("Can't copy data over for disk %d: %s",
5301 idx, result.fail_msg)
5302 errs.append(result.fail_msg)
5306 self.LogWarning("Some disks failed to copy, aborting")
5308 _RemoveDisks(self, instance, target_node=target_node)
5310 self.cfg.ReleaseDRBDMinors(instance.name)
5311 raise errors.OpExecError("Errors during disk copy: %s" %
5314 instance.primary_node = target_node
5315 self.cfg.Update(instance, feedback_fn)
5317 self.LogInfo("Removing the disks on the original node")
5318 _RemoveDisks(self, instance, target_node=source_node)
5320 # Only start the instance if it's marked as up
5321 if instance.admin_up:
5322 self.LogInfo("Starting instance %s on node %s",
5323 instance.name, target_node)
5325 disks_ok, _ = _AssembleInstanceDisks(self, instance,
5326 ignore_secondaries=True)
5328 _ShutdownInstanceDisks(self, instance)
5329 raise errors.OpExecError("Can't activate the instance's disks")
5331 result = self.rpc.call_instance_start(target_node, instance, None, None)
5332 msg = result.fail_msg
5334 _ShutdownInstanceDisks(self, instance)
5335 raise errors.OpExecError("Could not start instance %s on node %s: %s" %
5336 (instance.name, target_node, msg))
5339 class LUMigrateNode(LogicalUnit):
5340 """Migrate all instances from a node.
5343 HPATH = "node-migrate"
5344 HTYPE = constants.HTYPE_NODE
5345 _OP_REQP = ["node_name", "live"]
5348 def ExpandNames(self):
5349 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5351 self.needed_locks = {
5352 locking.LEVEL_NODE: [self.op.node_name],
5355 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
5357 # Create tasklets for migrating instances for all instances on this node
5361 for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name):
5362 logging.debug("Migrating instance %s", inst.name)
5363 names.append(inst.name)
5365 tasklets.append(TLMigrateInstance(self, inst.name, self.op.live, False))
5367 self.tasklets = tasklets
5369 # Declare instance locks
5370 self.needed_locks[locking.LEVEL_INSTANCE] = names
5372 def DeclareLocks(self, level):
5373 if level == locking.LEVEL_NODE:
5374 self._LockInstancesNodes()
5376 def BuildHooksEnv(self):
5379 This runs on the master, the primary and all the secondaries.
5383 "NODE_NAME": self.op.node_name,
5386 nl = [self.cfg.GetMasterNode()]
5388 return (env, nl, nl)
5391 class TLMigrateInstance(Tasklet):
5392 def __init__(self, lu, instance_name, live, cleanup):
5393 """Initializes this class.
5396 Tasklet.__init__(self, lu)
5399 self.instance_name = instance_name
5401 self.cleanup = cleanup
5403 def CheckPrereq(self):
5404 """Check prerequisites.
5406 This checks that the instance is in the cluster.
5409 instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
5410 instance = self.cfg.GetInstanceInfo(instance_name)
5411 assert instance is not None
5413 if instance.disk_template != constants.DT_DRBD8:
5414 raise errors.OpPrereqError("Instance's disk layout is not"
5415 " drbd8, cannot migrate.", errors.ECODE_STATE)
5417 secondary_nodes = instance.secondary_nodes
5418 if not secondary_nodes:
5419 raise errors.ConfigurationError("No secondary node but using"
5420 " drbd8 disk template")
5422 i_be = self.cfg.GetClusterInfo().FillBE(instance)
5424 target_node = secondary_nodes[0]
5425 # check memory requirements on the secondary node
5426 _CheckNodeFreeMemory(self, target_node, "migrating instance %s" %
5427 instance.name, i_be[constants.BE_MEMORY],
5428 instance.hypervisor)
5430 # check bridge existance
5431 _CheckInstanceBridgesExist(self, instance, node=target_node)
5433 if not self.cleanup:
5434 _CheckNodeNotDrained(self, target_node)
5435 result = self.rpc.call_instance_migratable(instance.primary_node,
5437 result.Raise("Can't migrate, please use failover",
5438 prereq=True, ecode=errors.ECODE_STATE)
5440 self.instance = instance
5442 def _WaitUntilSync(self):
5443 """Poll with custom rpc for disk sync.
5445 This uses our own step-based rpc call.
5448 self.feedback_fn("* wait until resync is done")
5452 result = self.rpc.call_drbd_wait_sync(self.all_nodes,
5454 self.instance.disks)
5456 for node, nres in result.items():
5457 nres.Raise("Cannot resync disks on node %s" % node)
5458 node_done, node_percent = nres.payload
5459 all_done = all_done and node_done
5460 if node_percent is not None:
5461 min_percent = min(min_percent, node_percent)
5463 if min_percent < 100:
5464 self.feedback_fn(" - progress: %.1f%%" % min_percent)
5467 def _EnsureSecondary(self, node):
5468 """Demote a node to secondary.
5471 self.feedback_fn("* switching node %s to secondary mode" % node)
5473 for dev in self.instance.disks:
5474 self.cfg.SetDiskID(dev, node)
5476 result = self.rpc.call_blockdev_close(node, self.instance.name,
5477 self.instance.disks)
5478 result.Raise("Cannot change disk to secondary on node %s" % node)
5480 def _GoStandalone(self):
5481 """Disconnect from the network.
5484 self.feedback_fn("* changing into standalone mode")
5485 result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
5486 self.instance.disks)
5487 for node, nres in result.items():
5488 nres.Raise("Cannot disconnect disks node %s" % node)
5490 def _GoReconnect(self, multimaster):
5491 """Reconnect to the network.
5497 msg = "single-master"
5498 self.feedback_fn("* changing disks into %s mode" % msg)
5499 result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
5500 self.instance.disks,
5501 self.instance.name, multimaster)
5502 for node, nres in result.items():
5503 nres.Raise("Cannot change disks config on node %s" % node)
5505 def _ExecCleanup(self):
5506 """Try to cleanup after a failed migration.
5508 The cleanup is done by:
5509 - check that the instance is running only on one node
5510 (and update the config if needed)
5511 - change disks on its secondary node to secondary
5512 - wait until disks are fully synchronized
5513 - disconnect from the network
5514 - change disks into single-master mode
5515 - wait again until disks are fully synchronized
5518 instance = self.instance
5519 target_node = self.target_node
5520 source_node = self.source_node
5522 # check running on only one node
5523 self.feedback_fn("* checking where the instance actually runs"
5524 " (if this hangs, the hypervisor might be in"
5526 ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
5527 for node, result in ins_l.items():
5528 result.Raise("Can't contact node %s" % node)
5530 runningon_source = instance.name in ins_l[source_node].payload
5531 runningon_target = instance.name in ins_l[target_node].payload
5533 if runningon_source and runningon_target:
5534 raise errors.OpExecError("Instance seems to be running on two nodes,"
5535 " or the hypervisor is confused. You will have"
5536 " to ensure manually that it runs only on one"
5537 " and restart this operation.")
5539 if not (runningon_source or runningon_target):
5540 raise errors.OpExecError("Instance does not seem to be running at all."
5541 " In this case, it's safer to repair by"
5542 " running 'gnt-instance stop' to ensure disk"
5543 " shutdown, and then restarting it.")
5545 if runningon_target:
5546 # the migration has actually succeeded, we need to update the config
5547 self.feedback_fn("* instance running on secondary node (%s),"
5548 " updating config" % target_node)
5549 instance.primary_node = target_node
5550 self.cfg.Update(instance, self.feedback_fn)
5551 demoted_node = source_node
5553 self.feedback_fn("* instance confirmed to be running on its"
5554 " primary node (%s)" % source_node)
5555 demoted_node = target_node
5557 self._EnsureSecondary(demoted_node)
5559 self._WaitUntilSync()
5560 except errors.OpExecError:
5561 # we ignore here errors, since if the device is standalone, it
5562 # won't be able to sync
5564 self._GoStandalone()
5565 self._GoReconnect(False)
5566 self._WaitUntilSync()
5568 self.feedback_fn("* done")
5570 def _RevertDiskStatus(self):
5571 """Try to revert the disk status after a failed migration.
5574 target_node = self.target_node
5576 self._EnsureSecondary(target_node)
5577 self._GoStandalone()
5578 self._GoReconnect(False)
5579 self._WaitUntilSync()
5580 except errors.OpExecError, err:
5581 self.lu.LogWarning("Migration failed and I can't reconnect the"
5582 " drives: error '%s'\n"
5583 "Please look and recover the instance status" %
5586 def _AbortMigration(self):
5587 """Call the hypervisor code to abort a started migration.
5590 instance = self.instance
5591 target_node = self.target_node
5592 migration_info = self.migration_info
5594 abort_result = self.rpc.call_finalize_migration(target_node,
5598 abort_msg = abort_result.fail_msg
5600 logging.error("Aborting migration failed on target node %s: %s",
5601 target_node, abort_msg)
5602 # Don't raise an exception here, as we stil have to try to revert the
5603 # disk status, even if this step failed.
5605 def _ExecMigration(self):
5606 """Migrate an instance.
5608 The migrate is done by:
5609 - change the disks into dual-master mode
5610 - wait until disks are fully synchronized again
5611 - migrate the instance
5612 - change disks on the new secondary node (the old primary) to secondary
5613 - wait until disks are fully synchronized
5614 - change disks into single-master mode
5617 instance = self.instance
5618 target_node = self.target_node
5619 source_node = self.source_node
5621 self.feedback_fn("* checking disk consistency between source and target")
5622 for dev in instance.disks:
5623 if not _CheckDiskConsistency(self, dev, target_node, False):
5624 raise errors.OpExecError("Disk %s is degraded or not fully"
5625 " synchronized on target node,"
5626 " aborting migrate." % dev.iv_name)
5628 # First get the migration information from the remote node
5629 result = self.rpc.call_migration_info(source_node, instance)
5630 msg = result.fail_msg
5632 log_err = ("Failed fetching source migration information from %s: %s" %
5634 logging.error(log_err)
5635 raise errors.OpExecError(log_err)
5637 self.migration_info = migration_info = result.payload
5639 # Then switch the disks to master/master mode
5640 self._EnsureSecondary(target_node)
5641 self._GoStandalone()
5642 self._GoReconnect(True)
5643 self._WaitUntilSync()
5645 self.feedback_fn("* preparing %s to accept the instance" % target_node)
5646 result = self.rpc.call_accept_instance(target_node,
5649 self.nodes_ip[target_node])
5651 msg = result.fail_msg
5653 logging.error("Instance pre-migration failed, trying to revert"
5654 " disk status: %s", msg)
5655 self.feedback_fn("Pre-migration failed, aborting")
5656 self._AbortMigration()
5657 self._RevertDiskStatus()
5658 raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
5659 (instance.name, msg))
5661 self.feedback_fn("* migrating instance to %s" % target_node)
5663 result = self.rpc.call_instance_migrate(source_node, instance,
5664 self.nodes_ip[target_node],
5666 msg = result.fail_msg
5668 logging.error("Instance migration failed, trying to revert"
5669 " disk status: %s", msg)
5670 self.feedback_fn("Migration failed, aborting")
5671 self._AbortMigration()
5672 self._RevertDiskStatus()
5673 raise errors.OpExecError("Could not migrate instance %s: %s" %
5674 (instance.name, msg))
5677 instance.primary_node = target_node
5678 # distribute new instance config to the other nodes
5679 self.cfg.Update(instance, self.feedback_fn)
5681 result = self.rpc.call_finalize_migration(target_node,
5685 msg = result.fail_msg
5687 logging.error("Instance migration succeeded, but finalization failed:"
5689 raise errors.OpExecError("Could not finalize instance migration: %s" %
5692 self._EnsureSecondary(source_node)
5693 self._WaitUntilSync()
5694 self._GoStandalone()
5695 self._GoReconnect(False)
5696 self._WaitUntilSync()
5698 self.feedback_fn("* done")
5700 def Exec(self, feedback_fn):
5701 """Perform the migration.
5704 feedback_fn("Migrating instance %s" % self.instance.name)
5706 self.feedback_fn = feedback_fn
5708 self.source_node = self.instance.primary_node
5709 self.target_node = self.instance.secondary_nodes[0]
5710 self.all_nodes = [self.source_node, self.target_node]
5712 self.source_node: self.cfg.GetNodeInfo(self.source_node).secondary_ip,
5713 self.target_node: self.cfg.GetNodeInfo(self.target_node).secondary_ip,
5717 return self._ExecCleanup()
5719 return self._ExecMigration()
5722 def _CreateBlockDev(lu, node, instance, device, force_create,
5724 """Create a tree of block devices on a given node.
5726 If this device type has to be created on secondaries, create it and
5729 If not, just recurse to children keeping the same 'force' value.
5731 @param lu: the lu on whose behalf we execute
5732 @param node: the node on which to create the device
5733 @type instance: L{objects.Instance}
5734 @param instance: the instance which owns the device
5735 @type device: L{objects.Disk}
5736 @param device: the device to create
5737 @type force_create: boolean
5738 @param force_create: whether to force creation of this device; this
5739 will be change to True whenever we find a device which has
5740 CreateOnSecondary() attribute
5741 @param info: the extra 'metadata' we should attach to the device
5742 (this will be represented as a LVM tag)
5743 @type force_open: boolean
5744 @param force_open: this parameter will be passes to the
5745 L{backend.BlockdevCreate} function where it specifies
5746 whether we run on primary or not, and it affects both
5747 the child assembly and the device own Open() execution
5750 if device.CreateOnSecondary():
5754 for child in device.children:
5755 _CreateBlockDev(lu, node, instance, child, force_create,
5758 if not force_create:
5761 _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
5764 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
5765 """Create a single block device on a given node.
5767 This will not recurse over children of the device, so they must be
5770 @param lu: the lu on whose behalf we execute
5771 @param node: the node on which to create the device
5772 @type instance: L{objects.Instance}
5773 @param instance: the instance which owns the device
5774 @type device: L{objects.Disk}
5775 @param device: the device to create
5776 @param info: the extra 'metadata' we should attach to the device
5777 (this will be represented as a LVM tag)
5778 @type force_open: boolean
5779 @param force_open: this parameter will be passes to the
5780 L{backend.BlockdevCreate} function where it specifies
5781 whether we run on primary or not, and it affects both
5782 the child assembly and the device own Open() execution
5785 lu.cfg.SetDiskID(device, node)
5786 result = lu.rpc.call_blockdev_create(node, device, device.size,
5787 instance.name, force_open, info)
5788 result.Raise("Can't create block device %s on"
5789 " node %s for instance %s" % (device, node, instance.name))
5790 if device.physical_id is None:
5791 device.physical_id = result.payload
5794 def _GenerateUniqueNames(lu, exts):
5795 """Generate a suitable LV name.
5797 This will generate a logical volume name for the given instance.
5802 new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
5803 results.append("%s%s" % (new_id, val))
5807 def _GenerateDRBD8Branch(lu, primary, secondary, size, names, iv_name,
5809 """Generate a drbd8 device complete with its children.
5812 port = lu.cfg.AllocatePort()
5813 vgname = lu.cfg.GetVGName()
5814 shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
5815 dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
5816 logical_id=(vgname, names[0]))
5817 dev_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
5818 logical_id=(vgname, names[1]))
5819 drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
5820 logical_id=(primary, secondary, port,
5823 children=[dev_data, dev_meta],
5828 def _GenerateDiskTemplate(lu, template_name,
5829 instance_name, primary_node,
5830 secondary_nodes, disk_info,
5831 file_storage_dir, file_driver,
5833 """Generate the entire disk layout for a given template type.
5836 #TODO: compute space requirements
5838 vgname = lu.cfg.GetVGName()
5839 disk_count = len(disk_info)
5841 if template_name == constants.DT_DISKLESS:
5843 elif template_name == constants.DT_PLAIN:
5844 if len(secondary_nodes) != 0:
5845 raise errors.ProgrammerError("Wrong template configuration")
5847 names = _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
5848 for i in range(disk_count)])
5849 for idx, disk in enumerate(disk_info):
5850 disk_index = idx + base_index
5851 disk_dev = objects.Disk(dev_type=constants.LD_LV, size=disk["size"],
5852 logical_id=(vgname, names[idx]),
5853 iv_name="disk/%d" % disk_index,
5855 disks.append(disk_dev)
5856 elif template_name == constants.DT_DRBD8:
5857 if len(secondary_nodes) != 1:
5858 raise errors.ProgrammerError("Wrong template configuration")
5859 remote_node = secondary_nodes[0]
5860 minors = lu.cfg.AllocateDRBDMinor(
5861 [primary_node, remote_node] * len(disk_info), instance_name)
5864 for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
5865 for i in range(disk_count)]):
5866 names.append(lv_prefix + "_data")
5867 names.append(lv_prefix + "_meta")
5868 for idx, disk in enumerate(disk_info):
5869 disk_index = idx + base_index
5870 disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
5871 disk["size"], names[idx*2:idx*2+2],
5872 "disk/%d" % disk_index,
5873 minors[idx*2], minors[idx*2+1])
5874 disk_dev.mode = disk["mode"]
5875 disks.append(disk_dev)
5876 elif template_name == constants.DT_FILE:
5877 if len(secondary_nodes) != 0:
5878 raise errors.ProgrammerError("Wrong template configuration")
5880 _RequireFileStorage()
5882 for idx, disk in enumerate(disk_info):
5883 disk_index = idx + base_index
5884 disk_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk["size"],
5885 iv_name="disk/%d" % disk_index,
5886 logical_id=(file_driver,
5887 "%s/disk%d" % (file_storage_dir,
5890 disks.append(disk_dev)
5892 raise errors.ProgrammerError("Invalid disk template '%s'" % template_name)
5896 def _GetInstanceInfoText(instance):
5897 """Compute that text that should be added to the disk's metadata.
5900 return "originstname+%s" % instance.name
5903 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
5904 """Create all disks for an instance.
5906 This abstracts away some work from AddInstance.
5908 @type lu: L{LogicalUnit}
5909 @param lu: the logical unit on whose behalf we execute
5910 @type instance: L{objects.Instance}
5911 @param instance: the instance whose disks we should create
5913 @param to_skip: list of indices to skip
5914 @type target_node: string
5915 @param target_node: if passed, overrides the target node for creation
5917 @return: the success of the creation
5920 info = _GetInstanceInfoText(instance)
5921 if target_node is None:
5922 pnode = instance.primary_node
5923 all_nodes = instance.all_nodes
5928 if instance.disk_template == constants.DT_FILE:
5929 file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
5930 result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
5932 result.Raise("Failed to create directory '%s' on"
5933 " node %s" % (file_storage_dir, pnode))
5935 # Note: this needs to be kept in sync with adding of disks in
5936 # LUSetInstanceParams
5937 for idx, device in enumerate(instance.disks):
5938 if to_skip and idx in to_skip:
5940 logging.info("Creating volume %s for instance %s",
5941 device.iv_name, instance.name)
5943 for node in all_nodes:
5944 f_create = node == pnode
5945 _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
5948 def _RemoveDisks(lu, instance, target_node=None):
5949 """Remove all disks for an instance.
5951 This abstracts away some work from `AddInstance()` and
5952 `RemoveInstance()`. Note that in case some of the devices couldn't
5953 be removed, the removal will continue with the other ones (compare
5954 with `_CreateDisks()`).
5956 @type lu: L{LogicalUnit}
5957 @param lu: the logical unit on whose behalf we execute
5958 @type instance: L{objects.Instance}
5959 @param instance: the instance whose disks we should remove
5960 @type target_node: string
5961 @param target_node: used to override the node on which to remove the disks
5963 @return: the success of the removal
5966 logging.info("Removing block devices for instance %s", instance.name)
5969 for device in instance.disks:
5971 edata = [(target_node, device)]
5973 edata = device.ComputeNodeTree(instance.primary_node)
5974 for node, disk in edata:
5975 lu.cfg.SetDiskID(disk, node)
5976 msg = lu.rpc.call_blockdev_remove(node, disk).fail_msg
5978 lu.LogWarning("Could not remove block device %s on node %s,"
5979 " continuing anyway: %s", device.iv_name, node, msg)
5982 if instance.disk_template == constants.DT_FILE:
5983 file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
5987 tgt = instance.primary_node
5988 result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
5990 lu.LogWarning("Could not remove directory '%s' on node %s: %s",
5991 file_storage_dir, instance.primary_node, result.fail_msg)
5997 def _ComputeDiskSize(disk_template, disks):
5998 """Compute disk size requirements in the volume group
6001 # Required free disk space as a function of disk and swap space
6003 constants.DT_DISKLESS: None,
6004 constants.DT_PLAIN: sum(d["size"] for d in disks),
6005 # 128 MB are added for drbd metadata for each disk
6006 constants.DT_DRBD8: sum(d["size"] + 128 for d in disks),
6007 constants.DT_FILE: None,
6010 if disk_template not in req_size_dict:
6011 raise errors.ProgrammerError("Disk template '%s' size requirement"
6012 " is unknown" % disk_template)
6014 return req_size_dict[disk_template]
6017 def _CheckHVParams(lu, nodenames, hvname, hvparams):
6018 """Hypervisor parameter validation.
6020 This function abstract the hypervisor parameter validation to be
6021 used in both instance create and instance modify.
6023 @type lu: L{LogicalUnit}
6024 @param lu: the logical unit for which we check
6025 @type nodenames: list
6026 @param nodenames: the list of nodes on which we should check
6027 @type hvname: string
6028 @param hvname: the name of the hypervisor we should use
6029 @type hvparams: dict
6030 @param hvparams: the parameters which we need to check
6031 @raise errors.OpPrereqError: if the parameters are not valid
6034 hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames,
6037 for node in nodenames:
6041 info.Raise("Hypervisor parameter validation failed on node %s" % node)
6044 class LUCreateInstance(LogicalUnit):
6045 """Create an instance.
6048 HPATH = "instance-add"
6049 HTYPE = constants.HTYPE_INSTANCE
6050 _OP_REQP = ["instance_name", "disks",
6052 "wait_for_sync", "ip_check", "nics",
6053 "hvparams", "beparams"]
6056 def CheckArguments(self):
6060 # set optional parameters to none if they don't exist
6061 for attr in ["pnode", "snode", "iallocator", "hypervisor",
6062 "disk_template", "identify_defaults"]:
6063 if not hasattr(self.op, attr):
6064 setattr(self.op, attr, None)
6066 # do not require name_check to ease forward/backward compatibility
6068 if not hasattr(self.op, "name_check"):
6069 self.op.name_check = True
6070 if not hasattr(self.op, "no_install"):
6071 self.op.no_install = False
6072 if self.op.no_install and self.op.start:
6073 self.LogInfo("No-installation mode selected, disabling startup")
6074 self.op.start = False
6075 # validate/normalize the instance name
6076 self.op.instance_name = utils.HostInfo.NormalizeName(self.op.instance_name)
6077 if self.op.ip_check and not self.op.name_check:
6078 # TODO: make the ip check more flexible and not depend on the name check
6079 raise errors.OpPrereqError("Cannot do ip checks without a name check",
6081 # check disk information: either all adopt, or no adopt
6082 has_adopt = has_no_adopt = False
6083 for disk in self.op.disks:
6088 if has_adopt and has_no_adopt:
6089 raise errors.OpPrereqError("Either all disks are adopted or none is",
6092 if self.op.disk_template != constants.DT_PLAIN:
6093 raise errors.OpPrereqError("Disk adoption is only supported for the"
6094 " 'plain' disk template",
6096 if self.op.iallocator is not None:
6097 raise errors.OpPrereqError("Disk adoption not allowed with an"
6098 " iallocator script", errors.ECODE_INVAL)
6099 if self.op.mode == constants.INSTANCE_IMPORT:
6100 raise errors.OpPrereqError("Disk adoption not allowed for"
6101 " instance import", errors.ECODE_INVAL)
6103 self.adopt_disks = has_adopt
6105 # verify creation mode
6106 if self.op.mode not in (constants.INSTANCE_CREATE,
6107 constants.INSTANCE_IMPORT):
6108 raise errors.OpPrereqError("Invalid instance creation mode '%s'" %
6109 self.op.mode, errors.ECODE_INVAL)
6111 # instance name verification
6112 if self.op.name_check:
6113 self.hostname1 = utils.GetHostInfo(self.op.instance_name)
6114 self.op.instance_name = self.hostname1.name
6115 # used in CheckPrereq for ip ping check
6116 self.check_ip = self.hostname1.ip
6118 self.check_ip = None
6120 # file storage checks
6121 if (self.op.file_driver and
6122 not self.op.file_driver in constants.FILE_DRIVER):
6123 raise errors.OpPrereqError("Invalid file driver name '%s'" %
6124 self.op.file_driver, errors.ECODE_INVAL)
6126 if self.op.file_storage_dir and os.path.isabs(self.op.file_storage_dir):
6127 raise errors.OpPrereqError("File storage directory path not absolute",
6130 ### Node/iallocator related checks
6131 if [self.op.iallocator, self.op.pnode].count(None) != 1:
6132 raise errors.OpPrereqError("One and only one of iallocator and primary"
6133 " node must be given",
6136 if self.op.mode == constants.INSTANCE_IMPORT:
6137 # On import force_variant must be True, because if we forced it at
6138 # initial install, our only chance when importing it back is that it
6140 self.op.force_variant = True
6142 if self.op.no_install:
6143 self.LogInfo("No-installation mode has no effect during import")
6145 else: # INSTANCE_CREATE
6146 if getattr(self.op, "os_type", None) is None:
6147 raise errors.OpPrereqError("No guest OS specified",
6149 self.op.force_variant = getattr(self.op, "force_variant", False)
6150 if self.op.disk_template is None:
6151 raise errors.OpPrereqError("No disk template specified",
6154 def ExpandNames(self):
6155 """ExpandNames for CreateInstance.
6157 Figure out the right locks for instance creation.
6160 self.needed_locks = {}
6162 instance_name = self.op.instance_name
6163 # this is just a preventive check, but someone might still add this
6164 # instance in the meantime, and creation will fail at lock-add time
6165 if instance_name in self.cfg.GetInstanceList():
6166 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
6167 instance_name, errors.ECODE_EXISTS)
6169 self.add_locks[locking.LEVEL_INSTANCE] = instance_name
6171 if self.op.iallocator:
6172 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6174 self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
6175 nodelist = [self.op.pnode]
6176 if self.op.snode is not None:
6177 self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
6178 nodelist.append(self.op.snode)
6179 self.needed_locks[locking.LEVEL_NODE] = nodelist
6181 # in case of import lock the source node too
6182 if self.op.mode == constants.INSTANCE_IMPORT:
6183 src_node = getattr(self.op, "src_node", None)
6184 src_path = getattr(self.op, "src_path", None)
6186 if src_path is None:
6187 self.op.src_path = src_path = self.op.instance_name
6189 if src_node is None:
6190 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6191 self.op.src_node = None
6192 if os.path.isabs(src_path):
6193 raise errors.OpPrereqError("Importing an instance from an absolute"
6194 " path requires a source node option.",
6197 self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
6198 if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
6199 self.needed_locks[locking.LEVEL_NODE].append(src_node)
6200 if not os.path.isabs(src_path):
6201 self.op.src_path = src_path = \
6202 utils.PathJoin(constants.EXPORT_DIR, src_path)
6204 def _RunAllocator(self):
6205 """Run the allocator based on input opcode.
6208 nics = [n.ToDict() for n in self.nics]
6209 ial = IAllocator(self.cfg, self.rpc,
6210 mode=constants.IALLOCATOR_MODE_ALLOC,
6211 name=self.op.instance_name,
6212 disk_template=self.op.disk_template,
6215 vcpus=self.be_full[constants.BE_VCPUS],
6216 mem_size=self.be_full[constants.BE_MEMORY],
6219 hypervisor=self.op.hypervisor,
6222 ial.Run(self.op.iallocator)
6225 raise errors.OpPrereqError("Can't compute nodes using"
6226 " iallocator '%s': %s" %
6227 (self.op.iallocator, ial.info),
6229 if len(ial.result) != ial.required_nodes:
6230 raise errors.OpPrereqError("iallocator '%s' returned invalid number"
6231 " of nodes (%s), required %s" %
6232 (self.op.iallocator, len(ial.result),
6233 ial.required_nodes), errors.ECODE_FAULT)
6234 self.op.pnode = ial.result[0]
6235 self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
6236 self.op.instance_name, self.op.iallocator,
6237 utils.CommaJoin(ial.result))
6238 if ial.required_nodes == 2:
6239 self.op.snode = ial.result[1]
6241 def BuildHooksEnv(self):
6244 This runs on master, primary and secondary nodes of the instance.
6248 "ADD_MODE": self.op.mode,
6250 if self.op.mode == constants.INSTANCE_IMPORT:
6251 env["SRC_NODE"] = self.op.src_node
6252 env["SRC_PATH"] = self.op.src_path
6253 env["SRC_IMAGES"] = self.src_images
6255 env.update(_BuildInstanceHookEnv(
6256 name=self.op.instance_name,
6257 primary_node=self.op.pnode,
6258 secondary_nodes=self.secondaries,
6259 status=self.op.start,
6260 os_type=self.op.os_type,
6261 memory=self.be_full[constants.BE_MEMORY],
6262 vcpus=self.be_full[constants.BE_VCPUS],
6263 nics=_NICListToTuple(self, self.nics),
6264 disk_template=self.op.disk_template,
6265 disks=[(d["size"], d["mode"]) for d in self.disks],
6268 hypervisor_name=self.op.hypervisor,
6271 nl = ([self.cfg.GetMasterNode(), self.op.pnode] +
6275 def _ReadExportInfo(self):
6276 """Reads the export information from disk.
6278 It will override the opcode source node and path with the actual
6279 information, if these two were not specified before.
6281 @return: the export information
6284 assert self.op.mode == constants.INSTANCE_IMPORT
6286 src_node = self.op.src_node
6287 src_path = self.op.src_path
6289 if src_node is None:
6290 locked_nodes = self.acquired_locks[locking.LEVEL_NODE]
6291 exp_list = self.rpc.call_export_list(locked_nodes)
6293 for node in exp_list:
6294 if exp_list[node].fail_msg:
6296 if src_path in exp_list[node].payload:
6298 self.op.src_node = src_node = node
6299 self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
6303 raise errors.OpPrereqError("No export found for relative path %s" %
6304 src_path, errors.ECODE_INVAL)
6306 _CheckNodeOnline(self, src_node)
6307 result = self.rpc.call_export_info(src_node, src_path)
6308 result.Raise("No export or invalid export found in dir %s" % src_path)
6310 export_info = objects.SerializableConfigParser.Loads(str(result.payload))
6311 if not export_info.has_section(constants.INISECT_EXP):
6312 raise errors.ProgrammerError("Corrupted export config",
6313 errors.ECODE_ENVIRON)
6315 ei_version = export_info.get(constants.INISECT_EXP, "version")
6316 if (int(ei_version) != constants.EXPORT_VERSION):
6317 raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
6318 (ei_version, constants.EXPORT_VERSION),
6319 errors.ECODE_ENVIRON)
6322 def _ReadExportParams(self, einfo):
6323 """Use export parameters as defaults.
6325 In case the opcode doesn't specify (as in override) some instance
6326 parameters, then try to use them from the export information, if
6330 self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
6332 if self.op.disk_template is None:
6333 if einfo.has_option(constants.INISECT_INS, "disk_template"):
6334 self.op.disk_template = einfo.get(constants.INISECT_INS,
6337 raise errors.OpPrereqError("No disk template specified and the export"
6338 " is missing the disk_template information",
6341 if not self.op.disks:
6342 if einfo.has_option(constants.INISECT_INS, "disk_count"):
6344 # TODO: import the disk iv_name too
6345 for idx in range(einfo.getint(constants.INISECT_INS, "disk_count")):
6346 disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
6347 disks.append({"size": disk_sz})
6348 self.op.disks = disks
6350 raise errors.OpPrereqError("No disk info specified and the export"
6351 " is missing the disk information",
6354 if (not self.op.nics and
6355 einfo.has_option(constants.INISECT_INS, "nic_count")):
6357 for idx in range(einfo.getint(constants.INISECT_INS, "nic_count")):
6359 for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
6360 v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
6365 if (self.op.hypervisor is None and
6366 einfo.has_option(constants.INISECT_INS, "hypervisor")):
6367 self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
6368 if einfo.has_section(constants.INISECT_HYP):
6369 # use the export parameters but do not override the ones
6370 # specified by the user
6371 for name, value in einfo.items(constants.INISECT_HYP):
6372 if name not in self.op.hvparams:
6373 self.op.hvparams[name] = value
6375 if einfo.has_section(constants.INISECT_BEP):
6376 # use the parameters, without overriding
6377 for name, value in einfo.items(constants.INISECT_BEP):
6378 if name not in self.op.beparams:
6379 self.op.beparams[name] = value
6381 # try to read the parameters old style, from the main section
6382 for name in constants.BES_PARAMETERS:
6383 if (name not in self.op.beparams and
6384 einfo.has_option(constants.INISECT_INS, name)):
6385 self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
6387 def _RevertToDefaults(self, cluster):
6388 """Revert the instance parameters to the default values.
6392 hv_defs = cluster.GetHVDefaults(self.op.hypervisor, self.op.os_type)
6393 for name in self.op.hvparams.keys():
6394 if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
6395 del self.op.hvparams[name]
6397 be_defs = cluster.beparams.get(constants.PP_DEFAULT, {})
6398 for name in self.op.beparams.keys():
6399 if name in be_defs and be_defs[name] == self.op.beparams[name]:
6400 del self.op.beparams[name]
6402 nic_defs = cluster.nicparams.get(constants.PP_DEFAULT, {})
6403 for nic in self.op.nics:
6404 for name in constants.NICS_PARAMETERS:
6405 if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
6408 def CheckPrereq(self):
6409 """Check prerequisites.
6412 if self.op.mode == constants.INSTANCE_IMPORT:
6413 export_info = self._ReadExportInfo()
6414 self._ReadExportParams(export_info)
6416 _CheckDiskTemplate(self.op.disk_template)
6418 if (not self.cfg.GetVGName() and
6419 self.op.disk_template not in constants.DTS_NOT_LVM):
6420 raise errors.OpPrereqError("Cluster does not support lvm-based"
6421 " instances", errors.ECODE_STATE)
6423 if self.op.hypervisor is None:
6424 self.op.hypervisor = self.cfg.GetHypervisorType()
6426 cluster = self.cfg.GetClusterInfo()
6427 enabled_hvs = cluster.enabled_hypervisors
6428 if self.op.hypervisor not in enabled_hvs:
6429 raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
6430 " cluster (%s)" % (self.op.hypervisor,
6431 ",".join(enabled_hvs)),
6434 # check hypervisor parameter syntax (locally)
6435 utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6436 filled_hvp = objects.FillDict(cluster.GetHVDefaults(self.op.hypervisor,
6439 hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
6440 hv_type.CheckParameterSyntax(filled_hvp)
6441 self.hv_full = filled_hvp
6442 # check that we don't specify global parameters on an instance
6443 _CheckGlobalHvParams(self.op.hvparams)
6445 # fill and remember the beparams dict
6446 utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6447 self.be_full = objects.FillDict(cluster.beparams[constants.PP_DEFAULT],
6450 # now that hvp/bep are in final format, let's reset to defaults,
6452 if self.op.identify_defaults:
6453 self._RevertToDefaults(cluster)
6457 for idx, nic in enumerate(self.op.nics):
6458 nic_mode_req = nic.get("mode", None)
6459 nic_mode = nic_mode_req
6460 if nic_mode is None:
6461 nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
6463 # in routed mode, for the first nic, the default ip is 'auto'
6464 if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
6465 default_ip_mode = constants.VALUE_AUTO
6467 default_ip_mode = constants.VALUE_NONE
6469 # ip validity checks
6470 ip = nic.get("ip", default_ip_mode)
6471 if ip is None or ip.lower() == constants.VALUE_NONE:
6473 elif ip.lower() == constants.VALUE_AUTO:
6474 if not self.op.name_check:
6475 raise errors.OpPrereqError("IP address set to auto but name checks"
6476 " have been skipped. Aborting.",
6478 nic_ip = self.hostname1.ip
6480 if not utils.IsValidIP(ip):
6481 raise errors.OpPrereqError("Given IP address '%s' doesn't look"
6482 " like a valid IP" % ip,
6486 # TODO: check the ip address for uniqueness
6487 if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
6488 raise errors.OpPrereqError("Routed nic mode requires an ip address",
6491 # MAC address verification
6492 mac = nic.get("mac", constants.VALUE_AUTO)
6493 if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
6494 mac = utils.NormalizeAndValidateMac(mac)
6497 self.cfg.ReserveMAC(mac, self.proc.GetECId())
6498 except errors.ReservationError:
6499 raise errors.OpPrereqError("MAC address %s already in use"
6500 " in cluster" % mac,
6501 errors.ECODE_NOTUNIQUE)
6503 # bridge verification
6504 bridge = nic.get("bridge", None)
6505 link = nic.get("link", None)
6507 raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
6508 " at the same time", errors.ECODE_INVAL)
6509 elif bridge and nic_mode == constants.NIC_MODE_ROUTED:
6510 raise errors.OpPrereqError("Cannot pass 'bridge' on a routed nic",
6517 nicparams[constants.NIC_MODE] = nic_mode_req
6519 nicparams[constants.NIC_LINK] = link
6521 check_params = objects.FillDict(cluster.nicparams[constants.PP_DEFAULT],
6523 objects.NIC.CheckParameterSyntax(check_params)
6524 self.nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
6526 # disk checks/pre-build
6528 for disk in self.op.disks:
6529 mode = disk.get("mode", constants.DISK_RDWR)
6530 if mode not in constants.DISK_ACCESS_SET:
6531 raise errors.OpPrereqError("Invalid disk access mode '%s'" %
6532 mode, errors.ECODE_INVAL)
6533 size = disk.get("size", None)
6535 raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
6538 except (TypeError, ValueError):
6539 raise errors.OpPrereqError("Invalid disk size '%s'" % size,
6541 new_disk = {"size": size, "mode": mode}
6543 new_disk["adopt"] = disk["adopt"]
6544 self.disks.append(new_disk)
6546 if self.op.mode == constants.INSTANCE_IMPORT:
6548 # Check that the new instance doesn't have less disks than the export
6549 instance_disks = len(self.disks)
6550 export_disks = export_info.getint(constants.INISECT_INS, 'disk_count')
6551 if instance_disks < export_disks:
6552 raise errors.OpPrereqError("Not enough disks to import."
6553 " (instance: %d, export: %d)" %
6554 (instance_disks, export_disks),
6558 for idx in range(export_disks):
6559 option = 'disk%d_dump' % idx
6560 if export_info.has_option(constants.INISECT_INS, option):
6561 # FIXME: are the old os-es, disk sizes, etc. useful?
6562 export_name = export_info.get(constants.INISECT_INS, option)
6563 image = utils.PathJoin(self.op.src_path, export_name)
6564 disk_images.append(image)
6566 disk_images.append(False)
6568 self.src_images = disk_images
6570 old_name = export_info.get(constants.INISECT_INS, 'name')
6572 exp_nic_count = export_info.getint(constants.INISECT_INS, 'nic_count')
6573 except (TypeError, ValueError), err:
6574 raise errors.OpPrereqError("Invalid export file, nic_count is not"
6575 " an integer: %s" % str(err),
6577 if self.op.instance_name == old_name:
6578 for idx, nic in enumerate(self.nics):
6579 if nic.mac == constants.VALUE_AUTO and exp_nic_count >= idx:
6580 nic_mac_ini = 'nic%d_mac' % idx
6581 nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
6583 # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
6585 # ip ping checks (we use the same ip that was resolved in ExpandNames)
6586 if self.op.ip_check:
6587 if utils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
6588 raise errors.OpPrereqError("IP %s of instance %s already in use" %
6589 (self.check_ip, self.op.instance_name),
6590 errors.ECODE_NOTUNIQUE)
6592 #### mac address generation
6593 # By generating here the mac address both the allocator and the hooks get
6594 # the real final mac address rather than the 'auto' or 'generate' value.
6595 # There is a race condition between the generation and the instance object
6596 # creation, which means that we know the mac is valid now, but we're not
6597 # sure it will be when we actually add the instance. If things go bad
6598 # adding the instance will abort because of a duplicate mac, and the
6599 # creation job will fail.
6600 for nic in self.nics:
6601 if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
6602 nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
6606 if self.op.iallocator is not None:
6607 self._RunAllocator()
6609 #### node related checks
6611 # check primary node
6612 self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
6613 assert self.pnode is not None, \
6614 "Cannot retrieve locked node %s" % self.op.pnode
6616 raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
6617 pnode.name, errors.ECODE_STATE)
6619 raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
6620 pnode.name, errors.ECODE_STATE)
6622 self.secondaries = []
6624 # mirror node verification
6625 if self.op.disk_template in constants.DTS_NET_MIRROR:
6626 if self.op.snode is None:
6627 raise errors.OpPrereqError("The networked disk templates need"
6628 " a mirror node", errors.ECODE_INVAL)
6629 if self.op.snode == pnode.name:
6630 raise errors.OpPrereqError("The secondary node cannot be the"
6631 " primary node.", errors.ECODE_INVAL)
6632 _CheckNodeOnline(self, self.op.snode)
6633 _CheckNodeNotDrained(self, self.op.snode)
6634 self.secondaries.append(self.op.snode)
6636 nodenames = [pnode.name] + self.secondaries
6638 req_size = _ComputeDiskSize(self.op.disk_template,
6641 # Check lv size requirements, if not adopting
6642 if req_size is not None and not self.adopt_disks:
6643 _CheckNodesFreeDisk(self, nodenames, req_size)
6645 if self.adopt_disks: # instead, we must check the adoption data
6646 all_lvs = set([i["adopt"] for i in self.disks])
6647 if len(all_lvs) != len(self.disks):
6648 raise errors.OpPrereqError("Duplicate volume names given for adoption",
6650 for lv_name in all_lvs:
6652 self.cfg.ReserveLV(lv_name, self.proc.GetECId())
6653 except errors.ReservationError:
6654 raise errors.OpPrereqError("LV named %s used by another instance" %
6655 lv_name, errors.ECODE_NOTUNIQUE)
6657 node_lvs = self.rpc.call_lv_list([pnode.name],
6658 self.cfg.GetVGName())[pnode.name]
6659 node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
6660 node_lvs = node_lvs.payload
6661 delta = all_lvs.difference(node_lvs.keys())
6663 raise errors.OpPrereqError("Missing logical volume(s): %s" %
6664 utils.CommaJoin(delta),
6666 online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
6668 raise errors.OpPrereqError("Online logical volumes found, cannot"
6669 " adopt: %s" % utils.CommaJoin(online_lvs),
6671 # update the size of disk based on what is found
6672 for dsk in self.disks:
6673 dsk["size"] = int(float(node_lvs[dsk["adopt"]][0]))
6675 _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
6677 _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
6679 _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
6681 # memory check on primary node
6683 _CheckNodeFreeMemory(self, self.pnode.name,
6684 "creating instance %s" % self.op.instance_name,
6685 self.be_full[constants.BE_MEMORY],
6688 self.dry_run_result = list(nodenames)
6690 def Exec(self, feedback_fn):
6691 """Create and add the instance to the cluster.
6694 instance = self.op.instance_name
6695 pnode_name = self.pnode.name
6697 ht_kind = self.op.hypervisor
6698 if ht_kind in constants.HTS_REQ_PORT:
6699 network_port = self.cfg.AllocatePort()
6703 if constants.ENABLE_FILE_STORAGE:
6704 # this is needed because os.path.join does not accept None arguments
6705 if self.op.file_storage_dir is None:
6706 string_file_storage_dir = ""
6708 string_file_storage_dir = self.op.file_storage_dir
6710 # build the full file storage dir path
6711 file_storage_dir = utils.PathJoin(self.cfg.GetFileStorageDir(),
6712 string_file_storage_dir, instance)
6714 file_storage_dir = ""
6716 disks = _GenerateDiskTemplate(self,
6717 self.op.disk_template,
6718 instance, pnode_name,
6722 self.op.file_driver,
6725 iobj = objects.Instance(name=instance, os=self.op.os_type,
6726 primary_node=pnode_name,
6727 nics=self.nics, disks=disks,
6728 disk_template=self.op.disk_template,
6730 network_port=network_port,
6731 beparams=self.op.beparams,
6732 hvparams=self.op.hvparams,
6733 hypervisor=self.op.hypervisor,
6736 if self.adopt_disks:
6737 # rename LVs to the newly-generated names; we need to construct
6738 # 'fake' LV disks with the old data, plus the new unique_id
6739 tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
6741 for t_dsk, a_dsk in zip (tmp_disks, self.disks):
6742 rename_to.append(t_dsk.logical_id)
6743 t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk["adopt"])
6744 self.cfg.SetDiskID(t_dsk, pnode_name)
6745 result = self.rpc.call_blockdev_rename(pnode_name,
6746 zip(tmp_disks, rename_to))
6747 result.Raise("Failed to rename adoped LVs")
6749 feedback_fn("* creating instance disks...")
6751 _CreateDisks(self, iobj)
6752 except errors.OpExecError:
6753 self.LogWarning("Device creation failed, reverting...")
6755 _RemoveDisks(self, iobj)
6757 self.cfg.ReleaseDRBDMinors(instance)
6760 feedback_fn("adding instance %s to cluster config" % instance)
6762 self.cfg.AddInstance(iobj, self.proc.GetECId())
6764 # Declare that we don't want to remove the instance lock anymore, as we've
6765 # added the instance to the config
6766 del self.remove_locks[locking.LEVEL_INSTANCE]
6767 # Unlock all the nodes
6768 if self.op.mode == constants.INSTANCE_IMPORT:
6769 nodes_keep = [self.op.src_node]
6770 nodes_release = [node for node in self.acquired_locks[locking.LEVEL_NODE]
6771 if node != self.op.src_node]
6772 self.context.glm.release(locking.LEVEL_NODE, nodes_release)
6773 self.acquired_locks[locking.LEVEL_NODE] = nodes_keep
6775 self.context.glm.release(locking.LEVEL_NODE)
6776 del self.acquired_locks[locking.LEVEL_NODE]
6778 if self.op.wait_for_sync:
6779 disk_abort = not _WaitForSync(self, iobj)
6780 elif iobj.disk_template in constants.DTS_NET_MIRROR:
6781 # make sure the disks are not degraded (still sync-ing is ok)
6783 feedback_fn("* checking mirrors status")
6784 disk_abort = not _WaitForSync(self, iobj, oneshot=True)
6789 _RemoveDisks(self, iobj)
6790 self.cfg.RemoveInstance(iobj.name)
6791 # Make sure the instance lock gets removed
6792 self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
6793 raise errors.OpExecError("There are some degraded disks for"
6796 if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
6797 if self.op.mode == constants.INSTANCE_CREATE:
6798 if not self.op.no_install:
6799 feedback_fn("* running the instance OS create scripts...")
6800 # FIXME: pass debug option from opcode to backend
6801 result = self.rpc.call_instance_os_add(pnode_name, iobj, False,
6802 self.op.debug_level)
6803 result.Raise("Could not add os for instance %s"
6804 " on node %s" % (instance, pnode_name))
6806 elif self.op.mode == constants.INSTANCE_IMPORT:
6807 feedback_fn("* running the instance OS import scripts...")
6811 for idx, image in enumerate(self.src_images):
6815 # FIXME: pass debug option from opcode to backend
6816 dt = masterd.instance.DiskTransfer("disk/%s" % idx,
6817 constants.IEIO_FILE, (image, ),
6818 constants.IEIO_SCRIPT,
6819 (iobj.disks[idx], idx),
6821 transfers.append(dt)
6824 masterd.instance.TransferInstanceData(self, feedback_fn,
6825 self.op.src_node, pnode_name,
6826 self.pnode.secondary_ip,
6828 if not compat.all(import_result):
6829 self.LogWarning("Some disks for instance %s on node %s were not"
6830 " imported successfully" % (instance, pnode_name))
6833 # also checked in the prereq part
6834 raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
6838 iobj.admin_up = True
6839 self.cfg.Update(iobj, feedback_fn)
6840 logging.info("Starting instance %s on node %s", instance, pnode_name)
6841 feedback_fn("* starting instance...")
6842 result = self.rpc.call_instance_start(pnode_name, iobj, None, None)
6843 result.Raise("Could not start instance")
6845 return list(iobj.all_nodes)
6848 class LUConnectConsole(NoHooksLU):
6849 """Connect to an instance's console.
6851 This is somewhat special in that it returns the command line that
6852 you need to run on the master node in order to connect to the
6856 _OP_REQP = ["instance_name"]
6859 def ExpandNames(self):
6860 self._ExpandAndLockInstance()
6862 def CheckPrereq(self):
6863 """Check prerequisites.
6865 This checks that the instance is in the cluster.
6868 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6869 assert self.instance is not None, \
6870 "Cannot retrieve locked instance %s" % self.op.instance_name
6871 _CheckNodeOnline(self, self.instance.primary_node)
6873 def Exec(self, feedback_fn):
6874 """Connect to the console of an instance
6877 instance = self.instance
6878 node = instance.primary_node
6880 node_insts = self.rpc.call_instance_list([node],
6881 [instance.hypervisor])[node]
6882 node_insts.Raise("Can't get node information from %s" % node)
6884 if instance.name not in node_insts.payload:
6885 raise errors.OpExecError("Instance %s is not running." % instance.name)
6887 logging.debug("Connecting to console of %s on %s", instance.name, node)
6889 hyper = hypervisor.GetHypervisor(instance.hypervisor)
6890 cluster = self.cfg.GetClusterInfo()
6891 # beparams and hvparams are passed separately, to avoid editing the
6892 # instance and then saving the defaults in the instance itself.
6893 hvparams = cluster.FillHV(instance)
6894 beparams = cluster.FillBE(instance)
6895 console_cmd = hyper.GetShellCommandForConsole(instance, hvparams, beparams)
6898 return self.ssh.BuildCmd(node, "root", console_cmd, batch=True, tty=True)
6901 class LUReplaceDisks(LogicalUnit):
6902 """Replace the disks of an instance.
6905 HPATH = "mirrors-replace"
6906 HTYPE = constants.HTYPE_INSTANCE
6907 _OP_REQP = ["instance_name", "mode", "disks"]
6910 def CheckArguments(self):
6911 if not hasattr(self.op, "remote_node"):
6912 self.op.remote_node = None
6913 if not hasattr(self.op, "iallocator"):
6914 self.op.iallocator = None
6915 if not hasattr(self.op, "early_release"):
6916 self.op.early_release = False
6918 TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
6921 def ExpandNames(self):
6922 self._ExpandAndLockInstance()
6924 if self.op.iallocator is not None:
6925 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6927 elif self.op.remote_node is not None:
6928 remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
6929 self.op.remote_node = remote_node
6931 # Warning: do not remove the locking of the new secondary here
6932 # unless DRBD8.AddChildren is changed to work in parallel;
6933 # currently it doesn't since parallel invocations of
6934 # FindUnusedMinor will conflict
6935 self.needed_locks[locking.LEVEL_NODE] = [remote_node]
6936 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
6939 self.needed_locks[locking.LEVEL_NODE] = []
6940 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6942 self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
6943 self.op.iallocator, self.op.remote_node,
6944 self.op.disks, False, self.op.early_release)
6946 self.tasklets = [self.replacer]
6948 def DeclareLocks(self, level):
6949 # If we're not already locking all nodes in the set we have to declare the
6950 # instance's primary/secondary nodes.
6951 if (level == locking.LEVEL_NODE and
6952 self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
6953 self._LockInstancesNodes()
6955 def BuildHooksEnv(self):
6958 This runs on the master, the primary and all the secondaries.
6961 instance = self.replacer.instance
6963 "MODE": self.op.mode,
6964 "NEW_SECONDARY": self.op.remote_node,
6965 "OLD_SECONDARY": instance.secondary_nodes[0],
6967 env.update(_BuildInstanceHookEnvByObject(self, instance))
6969 self.cfg.GetMasterNode(),
6970 instance.primary_node,
6972 if self.op.remote_node is not None:
6973 nl.append(self.op.remote_node)
6977 class LUEvacuateNode(LogicalUnit):
6978 """Relocate the secondary instances from a node.
6981 HPATH = "node-evacuate"
6982 HTYPE = constants.HTYPE_NODE
6983 _OP_REQP = ["node_name"]
6986 def CheckArguments(self):
6987 if not hasattr(self.op, "remote_node"):
6988 self.op.remote_node = None
6989 if not hasattr(self.op, "iallocator"):
6990 self.op.iallocator = None
6991 if not hasattr(self.op, "early_release"):
6992 self.op.early_release = False
6994 TLReplaceDisks.CheckArguments(constants.REPLACE_DISK_CHG,
6995 self.op.remote_node,
6998 def ExpandNames(self):
6999 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
7001 self.needed_locks = {}
7003 # Declare node locks
7004 if self.op.iallocator is not None:
7005 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7007 elif self.op.remote_node is not None:
7008 self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
7010 # Warning: do not remove the locking of the new secondary here
7011 # unless DRBD8.AddChildren is changed to work in parallel;
7012 # currently it doesn't since parallel invocations of
7013 # FindUnusedMinor will conflict
7014 self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
7015 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7018 raise errors.OpPrereqError("Invalid parameters", errors.ECODE_INVAL)
7020 # Create tasklets for replacing disks for all secondary instances on this
7025 for inst in _GetNodeSecondaryInstances(self.cfg, self.op.node_name):
7026 logging.debug("Replacing disks for instance %s", inst.name)
7027 names.append(inst.name)
7029 replacer = TLReplaceDisks(self, inst.name, constants.REPLACE_DISK_CHG,
7030 self.op.iallocator, self.op.remote_node, [],
7031 True, self.op.early_release)
7032 tasklets.append(replacer)
7034 self.tasklets = tasklets
7035 self.instance_names = names
7037 # Declare instance locks
7038 self.needed_locks[locking.LEVEL_INSTANCE] = self.instance_names
7040 def DeclareLocks(self, level):
7041 # If we're not already locking all nodes in the set we have to declare the
7042 # instance's primary/secondary nodes.
7043 if (level == locking.LEVEL_NODE and
7044 self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
7045 self._LockInstancesNodes()
7047 def BuildHooksEnv(self):
7050 This runs on the master, the primary and all the secondaries.
7054 "NODE_NAME": self.op.node_name,
7057 nl = [self.cfg.GetMasterNode()]
7059 if self.op.remote_node is not None:
7060 env["NEW_SECONDARY"] = self.op.remote_node
7061 nl.append(self.op.remote_node)
7063 return (env, nl, nl)
7066 class TLReplaceDisks(Tasklet):
7067 """Replaces disks for an instance.
7069 Note: Locking is not within the scope of this class.
7072 def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
7073 disks, delay_iallocator, early_release):
7074 """Initializes this class.
7077 Tasklet.__init__(self, lu)
7080 self.instance_name = instance_name
7082 self.iallocator_name = iallocator_name
7083 self.remote_node = remote_node
7085 self.delay_iallocator = delay_iallocator
7086 self.early_release = early_release
7089 self.instance = None
7090 self.new_node = None
7091 self.target_node = None
7092 self.other_node = None
7093 self.remote_node_info = None
7094 self.node_secondary_ip = None
7097 def CheckArguments(mode, remote_node, iallocator):
7098 """Helper function for users of this class.
7101 # check for valid parameter combination
7102 if mode == constants.REPLACE_DISK_CHG:
7103 if remote_node is None and iallocator is None:
7104 raise errors.OpPrereqError("When changing the secondary either an"
7105 " iallocator script must be used or the"
7106 " new node given", errors.ECODE_INVAL)
7108 if remote_node is not None and iallocator is not None:
7109 raise errors.OpPrereqError("Give either the iallocator or the new"
7110 " secondary, not both", errors.ECODE_INVAL)
7112 elif remote_node is not None or iallocator is not None:
7113 # Not replacing the secondary
7114 raise errors.OpPrereqError("The iallocator and new node options can"
7115 " only be used when changing the"
7116 " secondary node", errors.ECODE_INVAL)
7119 def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
7120 """Compute a new secondary node using an IAllocator.
7123 ial = IAllocator(lu.cfg, lu.rpc,
7124 mode=constants.IALLOCATOR_MODE_RELOC,
7126 relocate_from=relocate_from)
7128 ial.Run(iallocator_name)
7131 raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
7132 " %s" % (iallocator_name, ial.info),
7135 if len(ial.result) != ial.required_nodes:
7136 raise errors.OpPrereqError("iallocator '%s' returned invalid number"
7137 " of nodes (%s), required %s" %
7139 len(ial.result), ial.required_nodes),
7142 remote_node_name = ial.result[0]
7144 lu.LogInfo("Selected new secondary for instance '%s': %s",
7145 instance_name, remote_node_name)
7147 return remote_node_name
7149 def _FindFaultyDisks(self, node_name):
7150 return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
7153 def CheckPrereq(self):
7154 """Check prerequisites.
7156 This checks that the instance is in the cluster.
7159 self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
7160 assert instance is not None, \
7161 "Cannot retrieve locked instance %s" % self.instance_name
7163 if instance.disk_template != constants.DT_DRBD8:
7164 raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
7165 " instances", errors.ECODE_INVAL)
7167 if len(instance.secondary_nodes) != 1:
7168 raise errors.OpPrereqError("The instance has a strange layout,"
7169 " expected one secondary but found %d" %
7170 len(instance.secondary_nodes),
7173 if not self.delay_iallocator:
7174 self._CheckPrereq2()
7176 def _CheckPrereq2(self):
7177 """Check prerequisites, second part.
7179 This function should always be part of CheckPrereq. It was separated and is
7180 now called from Exec because during node evacuation iallocator was only
7181 called with an unmodified cluster model, not taking planned changes into
7185 instance = self.instance
7186 secondary_node = instance.secondary_nodes[0]
7188 if self.iallocator_name is None:
7189 remote_node = self.remote_node
7191 remote_node = self._RunAllocator(self.lu, self.iallocator_name,
7192 instance.name, instance.secondary_nodes)
7194 if remote_node is not None:
7195 self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
7196 assert self.remote_node_info is not None, \
7197 "Cannot retrieve locked node %s" % remote_node
7199 self.remote_node_info = None
7201 if remote_node == self.instance.primary_node:
7202 raise errors.OpPrereqError("The specified node is the primary node of"
7203 " the instance.", errors.ECODE_INVAL)
7205 if remote_node == secondary_node:
7206 raise errors.OpPrereqError("The specified node is already the"
7207 " secondary node of the instance.",
7210 if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
7211 constants.REPLACE_DISK_CHG):
7212 raise errors.OpPrereqError("Cannot specify disks to be replaced",
7215 if self.mode == constants.REPLACE_DISK_AUTO:
7216 faulty_primary = self._FindFaultyDisks(instance.primary_node)
7217 faulty_secondary = self._FindFaultyDisks(secondary_node)
7219 if faulty_primary and faulty_secondary:
7220 raise errors.OpPrereqError("Instance %s has faulty disks on more than"
7221 " one node and can not be repaired"
7222 " automatically" % self.instance_name,
7226 self.disks = faulty_primary
7227 self.target_node = instance.primary_node
7228 self.other_node = secondary_node
7229 check_nodes = [self.target_node, self.other_node]
7230 elif faulty_secondary:
7231 self.disks = faulty_secondary
7232 self.target_node = secondary_node
7233 self.other_node = instance.primary_node
7234 check_nodes = [self.target_node, self.other_node]
7240 # Non-automatic modes
7241 if self.mode == constants.REPLACE_DISK_PRI:
7242 self.target_node = instance.primary_node
7243 self.other_node = secondary_node
7244 check_nodes = [self.target_node, self.other_node]
7246 elif self.mode == constants.REPLACE_DISK_SEC:
7247 self.target_node = secondary_node
7248 self.other_node = instance.primary_node
7249 check_nodes = [self.target_node, self.other_node]
7251 elif self.mode == constants.REPLACE_DISK_CHG:
7252 self.new_node = remote_node
7253 self.other_node = instance.primary_node
7254 self.target_node = secondary_node
7255 check_nodes = [self.new_node, self.other_node]
7257 _CheckNodeNotDrained(self.lu, remote_node)
7259 old_node_info = self.cfg.GetNodeInfo(secondary_node)
7260 assert old_node_info is not None
7261 if old_node_info.offline and not self.early_release:
7262 # doesn't make sense to delay the release
7263 self.early_release = True
7264 self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
7265 " early-release mode", secondary_node)
7268 raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
7271 # If not specified all disks should be replaced
7273 self.disks = range(len(self.instance.disks))
7275 for node in check_nodes:
7276 _CheckNodeOnline(self.lu, node)
7278 # Check whether disks are valid
7279 for disk_idx in self.disks:
7280 instance.FindDisk(disk_idx)
7282 # Get secondary node IP addresses
7285 for node_name in [self.target_node, self.other_node, self.new_node]:
7286 if node_name is not None:
7287 node_2nd_ip[node_name] = self.cfg.GetNodeInfo(node_name).secondary_ip
7289 self.node_secondary_ip = node_2nd_ip
7291 def Exec(self, feedback_fn):
7292 """Execute disk replacement.
7294 This dispatches the disk replacement to the appropriate handler.
7297 if self.delay_iallocator:
7298 self._CheckPrereq2()
7301 feedback_fn("No disks need replacement")
7304 feedback_fn("Replacing disk(s) %s for %s" %
7305 (utils.CommaJoin(self.disks), self.instance.name))
7307 activate_disks = (not self.instance.admin_up)
7309 # Activate the instance disks if we're replacing them on a down instance
7311 _StartInstanceDisks(self.lu, self.instance, True)
7314 # Should we replace the secondary node?
7315 if self.new_node is not None:
7316 fn = self._ExecDrbd8Secondary
7318 fn = self._ExecDrbd8DiskOnly
7320 return fn(feedback_fn)
7323 # Deactivate the instance disks if we're replacing them on a
7326 _SafeShutdownInstanceDisks(self.lu, self.instance)
7328 def _CheckVolumeGroup(self, nodes):
7329 self.lu.LogInfo("Checking volume groups")
7331 vgname = self.cfg.GetVGName()
7333 # Make sure volume group exists on all involved nodes
7334 results = self.rpc.call_vg_list(nodes)
7336 raise errors.OpExecError("Can't list volume groups on the nodes")
7340 res.Raise("Error checking node %s" % node)
7341 if vgname not in res.payload:
7342 raise errors.OpExecError("Volume group '%s' not found on node %s" %
7345 def _CheckDisksExistence(self, nodes):
7346 # Check disk existence
7347 for idx, dev in enumerate(self.instance.disks):
7348 if idx not in self.disks:
7352 self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
7353 self.cfg.SetDiskID(dev, node)
7355 result = self.rpc.call_blockdev_find(node, dev)
7357 msg = result.fail_msg
7358 if msg or not result.payload:
7360 msg = "disk not found"
7361 raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
7364 def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
7365 for idx, dev in enumerate(self.instance.disks):
7366 if idx not in self.disks:
7369 self.lu.LogInfo("Checking disk/%d consistency on node %s" %
7372 if not _CheckDiskConsistency(self.lu, dev, node_name, on_primary,
7374 raise errors.OpExecError("Node %s has degraded storage, unsafe to"
7375 " replace disks for instance %s" %
7376 (node_name, self.instance.name))
7378 def _CreateNewStorage(self, node_name):
7379 vgname = self.cfg.GetVGName()
7382 for idx, dev in enumerate(self.instance.disks):
7383 if idx not in self.disks:
7386 self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
7388 self.cfg.SetDiskID(dev, node_name)
7390 lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
7391 names = _GenerateUniqueNames(self.lu, lv_names)
7393 lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
7394 logical_id=(vgname, names[0]))
7395 lv_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
7396 logical_id=(vgname, names[1]))
7398 new_lvs = [lv_data, lv_meta]
7399 old_lvs = dev.children
7400 iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
7402 # we pass force_create=True to force the LVM creation
7403 for new_lv in new_lvs:
7404 _CreateBlockDev(self.lu, node_name, self.instance, new_lv, True,
7405 _GetInstanceInfoText(self.instance), False)
7409 def _CheckDevices(self, node_name, iv_names):
7410 for name, (dev, _, _) in iv_names.iteritems():
7411 self.cfg.SetDiskID(dev, node_name)
7413 result = self.rpc.call_blockdev_find(node_name, dev)
7415 msg = result.fail_msg
7416 if msg or not result.payload:
7418 msg = "disk not found"
7419 raise errors.OpExecError("Can't find DRBD device %s: %s" %
7422 if result.payload.is_degraded:
7423 raise errors.OpExecError("DRBD device %s is degraded!" % name)
7425 def _RemoveOldStorage(self, node_name, iv_names):
7426 for name, (_, old_lvs, _) in iv_names.iteritems():
7427 self.lu.LogInfo("Remove logical volumes for %s" % name)
7430 self.cfg.SetDiskID(lv, node_name)
7432 msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
7434 self.lu.LogWarning("Can't remove old LV: %s" % msg,
7435 hint="remove unused LVs manually")
7437 def _ReleaseNodeLock(self, node_name):
7438 """Releases the lock for a given node."""
7439 self.lu.context.glm.release(locking.LEVEL_NODE, node_name)
7441 def _ExecDrbd8DiskOnly(self, feedback_fn):
7442 """Replace a disk on the primary or secondary for DRBD 8.
7444 The algorithm for replace is quite complicated:
7446 1. for each disk to be replaced:
7448 1. create new LVs on the target node with unique names
7449 1. detach old LVs from the drbd device
7450 1. rename old LVs to name_replaced.<time_t>
7451 1. rename new LVs to old LVs
7452 1. attach the new LVs (with the old names now) to the drbd device
7454 1. wait for sync across all devices
7456 1. for each modified disk:
7458 1. remove old LVs (which have the name name_replaces.<time_t>)
7460 Failures are not very well handled.
7465 # Step: check device activation
7466 self.lu.LogStep(1, steps_total, "Check device existence")
7467 self._CheckDisksExistence([self.other_node, self.target_node])
7468 self._CheckVolumeGroup([self.target_node, self.other_node])
7470 # Step: check other node consistency
7471 self.lu.LogStep(2, steps_total, "Check peer consistency")
7472 self._CheckDisksConsistency(self.other_node,
7473 self.other_node == self.instance.primary_node,
7476 # Step: create new storage
7477 self.lu.LogStep(3, steps_total, "Allocate new storage")
7478 iv_names = self._CreateNewStorage(self.target_node)
7480 # Step: for each lv, detach+rename*2+attach
7481 self.lu.LogStep(4, steps_total, "Changing drbd configuration")
7482 for dev, old_lvs, new_lvs in iv_names.itervalues():
7483 self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
7485 result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
7487 result.Raise("Can't detach drbd from local storage on node"
7488 " %s for device %s" % (self.target_node, dev.iv_name))
7490 #cfg.Update(instance)
7492 # ok, we created the new LVs, so now we know we have the needed
7493 # storage; as such, we proceed on the target node to rename
7494 # old_lv to _old, and new_lv to old_lv; note that we rename LVs
7495 # using the assumption that logical_id == physical_id (which in
7496 # turn is the unique_id on that node)
7498 # FIXME(iustin): use a better name for the replaced LVs
7499 temp_suffix = int(time.time())
7500 ren_fn = lambda d, suff: (d.physical_id[0],
7501 d.physical_id[1] + "_replaced-%s" % suff)
7503 # Build the rename list based on what LVs exist on the node
7504 rename_old_to_new = []
7505 for to_ren in old_lvs:
7506 result = self.rpc.call_blockdev_find(self.target_node, to_ren)
7507 if not result.fail_msg and result.payload:
7509 rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
7511 self.lu.LogInfo("Renaming the old LVs on the target node")
7512 result = self.rpc.call_blockdev_rename(self.target_node,
7514 result.Raise("Can't rename old LVs on node %s" % self.target_node)
7516 # Now we rename the new LVs to the old LVs
7517 self.lu.LogInfo("Renaming the new LVs on the target node")
7518 rename_new_to_old = [(new, old.physical_id)
7519 for old, new in zip(old_lvs, new_lvs)]
7520 result = self.rpc.call_blockdev_rename(self.target_node,
7522 result.Raise("Can't rename new LVs on node %s" % self.target_node)
7524 for old, new in zip(old_lvs, new_lvs):
7525 new.logical_id = old.logical_id
7526 self.cfg.SetDiskID(new, self.target_node)
7528 for disk in old_lvs:
7529 disk.logical_id = ren_fn(disk, temp_suffix)
7530 self.cfg.SetDiskID(disk, self.target_node)
7532 # Now that the new lvs have the old name, we can add them to the device
7533 self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
7534 result = self.rpc.call_blockdev_addchildren(self.target_node, dev,
7536 msg = result.fail_msg
7538 for new_lv in new_lvs:
7539 msg2 = self.rpc.call_blockdev_remove(self.target_node,
7542 self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
7543 hint=("cleanup manually the unused logical"
7545 raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
7547 dev.children = new_lvs
7549 self.cfg.Update(self.instance, feedback_fn)
7552 if self.early_release:
7553 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7555 self._RemoveOldStorage(self.target_node, iv_names)
7556 # WARNING: we release both node locks here, do not do other RPCs
7557 # than WaitForSync to the primary node
7558 self._ReleaseNodeLock([self.target_node, self.other_node])
7561 # This can fail as the old devices are degraded and _WaitForSync
7562 # does a combined result over all disks, so we don't check its return value
7563 self.lu.LogStep(cstep, steps_total, "Sync devices")
7565 _WaitForSync(self.lu, self.instance)
7567 # Check all devices manually
7568 self._CheckDevices(self.instance.primary_node, iv_names)
7570 # Step: remove old storage
7571 if not self.early_release:
7572 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7574 self._RemoveOldStorage(self.target_node, iv_names)
7576 def _ExecDrbd8Secondary(self, feedback_fn):
7577 """Replace the secondary node for DRBD 8.
7579 The algorithm for replace is quite complicated:
7580 - for all disks of the instance:
7581 - create new LVs on the new node with same names
7582 - shutdown the drbd device on the old secondary
7583 - disconnect the drbd network on the primary
7584 - create the drbd device on the new secondary
7585 - network attach the drbd on the primary, using an artifice:
7586 the drbd code for Attach() will connect to the network if it
7587 finds a device which is connected to the good local disks but
7589 - wait for sync across all devices
7590 - remove all disks from the old secondary
7592 Failures are not very well handled.
7597 # Step: check device activation
7598 self.lu.LogStep(1, steps_total, "Check device existence")
7599 self._CheckDisksExistence([self.instance.primary_node])
7600 self._CheckVolumeGroup([self.instance.primary_node])
7602 # Step: check other node consistency
7603 self.lu.LogStep(2, steps_total, "Check peer consistency")
7604 self._CheckDisksConsistency(self.instance.primary_node, True, True)
7606 # Step: create new storage
7607 self.lu.LogStep(3, steps_total, "Allocate new storage")
7608 for idx, dev in enumerate(self.instance.disks):
7609 self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
7610 (self.new_node, idx))
7611 # we pass force_create=True to force LVM creation
7612 for new_lv in dev.children:
7613 _CreateBlockDev(self.lu, self.new_node, self.instance, new_lv, True,
7614 _GetInstanceInfoText(self.instance), False)
7616 # Step 4: dbrd minors and drbd setups changes
7617 # after this, we must manually remove the drbd minors on both the
7618 # error and the success paths
7619 self.lu.LogStep(4, steps_total, "Changing drbd configuration")
7620 minors = self.cfg.AllocateDRBDMinor([self.new_node
7621 for dev in self.instance.disks],
7623 logging.debug("Allocated minors %r", minors)
7626 for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
7627 self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
7628 (self.new_node, idx))
7629 # create new devices on new_node; note that we create two IDs:
7630 # one without port, so the drbd will be activated without
7631 # networking information on the new node at this stage, and one
7632 # with network, for the latter activation in step 4
7633 (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
7634 if self.instance.primary_node == o_node1:
7637 assert self.instance.primary_node == o_node2, "Three-node instance?"
7640 new_alone_id = (self.instance.primary_node, self.new_node, None,
7641 p_minor, new_minor, o_secret)
7642 new_net_id = (self.instance.primary_node, self.new_node, o_port,
7643 p_minor, new_minor, o_secret)
7645 iv_names[idx] = (dev, dev.children, new_net_id)
7646 logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
7648 new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
7649 logical_id=new_alone_id,
7650 children=dev.children,
7653 _CreateSingleBlockDev(self.lu, self.new_node, self.instance, new_drbd,
7654 _GetInstanceInfoText(self.instance), False)
7655 except errors.GenericError:
7656 self.cfg.ReleaseDRBDMinors(self.instance.name)
7659 # We have new devices, shutdown the drbd on the old secondary
7660 for idx, dev in enumerate(self.instance.disks):
7661 self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
7662 self.cfg.SetDiskID(dev, self.target_node)
7663 msg = self.rpc.call_blockdev_shutdown(self.target_node, dev).fail_msg
7665 self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
7666 "node: %s" % (idx, msg),
7667 hint=("Please cleanup this device manually as"
7668 " soon as possible"))
7670 self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
7671 result = self.rpc.call_drbd_disconnect_net([self.instance.primary_node],
7672 self.node_secondary_ip,
7673 self.instance.disks)\
7674 [self.instance.primary_node]
7676 msg = result.fail_msg
7678 # detaches didn't succeed (unlikely)
7679 self.cfg.ReleaseDRBDMinors(self.instance.name)
7680 raise errors.OpExecError("Can't detach the disks from the network on"
7681 " old node: %s" % (msg,))
7683 # if we managed to detach at least one, we update all the disks of
7684 # the instance to point to the new secondary
7685 self.lu.LogInfo("Updating instance configuration")
7686 for dev, _, new_logical_id in iv_names.itervalues():
7687 dev.logical_id = new_logical_id
7688 self.cfg.SetDiskID(dev, self.instance.primary_node)
7690 self.cfg.Update(self.instance, feedback_fn)
7692 # and now perform the drbd attach
7693 self.lu.LogInfo("Attaching primary drbds to new secondary"
7694 " (standalone => connected)")
7695 result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
7697 self.node_secondary_ip,
7698 self.instance.disks,
7701 for to_node, to_result in result.items():
7702 msg = to_result.fail_msg
7704 self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
7706 hint=("please do a gnt-instance info to see the"
7707 " status of disks"))
7709 if self.early_release:
7710 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7712 self._RemoveOldStorage(self.target_node, iv_names)
7713 # WARNING: we release all node locks here, do not do other RPCs
7714 # than WaitForSync to the primary node
7715 self._ReleaseNodeLock([self.instance.primary_node,
7720 # This can fail as the old devices are degraded and _WaitForSync
7721 # does a combined result over all disks, so we don't check its return value
7722 self.lu.LogStep(cstep, steps_total, "Sync devices")
7724 _WaitForSync(self.lu, self.instance)
7726 # Check all devices manually
7727 self._CheckDevices(self.instance.primary_node, iv_names)
7729 # Step: remove old storage
7730 if not self.early_release:
7731 self.lu.LogStep(cstep, steps_total, "Removing old storage")
7732 self._RemoveOldStorage(self.target_node, iv_names)
7735 class LURepairNodeStorage(NoHooksLU):
7736 """Repairs the volume group on a node.
7739 _OP_REQP = ["node_name"]
7742 def CheckArguments(self):
7743 self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
7745 _CheckStorageType(self.op.storage_type)
7747 def ExpandNames(self):
7748 self.needed_locks = {
7749 locking.LEVEL_NODE: [self.op.node_name],
7752 def _CheckFaultyDisks(self, instance, node_name):
7753 """Ensure faulty disks abort the opcode or at least warn."""
7755 if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
7757 raise errors.OpPrereqError("Instance '%s' has faulty disks on"
7758 " node '%s'" % (instance.name, node_name),
7760 except errors.OpPrereqError, err:
7761 if self.op.ignore_consistency:
7762 self.proc.LogWarning(str(err.args[0]))
7766 def CheckPrereq(self):
7767 """Check prerequisites.
7770 storage_type = self.op.storage_type
7772 if (constants.SO_FIX_CONSISTENCY not in
7773 constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
7774 raise errors.OpPrereqError("Storage units of type '%s' can not be"
7775 " repaired" % storage_type,
7778 # Check whether any instance on this node has faulty disks
7779 for inst in _GetNodeInstances(self.cfg, self.op.node_name):
7780 if not inst.admin_up:
7782 check_nodes = set(inst.all_nodes)
7783 check_nodes.discard(self.op.node_name)
7784 for inst_node_name in check_nodes:
7785 self._CheckFaultyDisks(inst, inst_node_name)
7787 def Exec(self, feedback_fn):
7788 feedback_fn("Repairing storage unit '%s' on %s ..." %
7789 (self.op.name, self.op.node_name))
7791 st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
7792 result = self.rpc.call_storage_execute(self.op.node_name,
7793 self.op.storage_type, st_args,
7795 constants.SO_FIX_CONSISTENCY)
7796 result.Raise("Failed to repair storage unit '%s' on %s" %
7797 (self.op.name, self.op.node_name))
7800 class LUNodeEvacuationStrategy(NoHooksLU):
7801 """Computes the node evacuation strategy.
7804 _OP_REQP = ["nodes"]
7807 def CheckArguments(self):
7808 if not hasattr(self.op, "remote_node"):
7809 self.op.remote_node = None
7810 if not hasattr(self.op, "iallocator"):
7811 self.op.iallocator = None
7812 if self.op.remote_node is not None and self.op.iallocator is not None:
7813 raise errors.OpPrereqError("Give either the iallocator or the new"
7814 " secondary, not both", errors.ECODE_INVAL)
7816 def ExpandNames(self):
7817 self.op.nodes = _GetWantedNodes(self, self.op.nodes)
7818 self.needed_locks = locks = {}
7819 if self.op.remote_node is None:
7820 locks[locking.LEVEL_NODE] = locking.ALL_SET
7822 self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
7823 locks[locking.LEVEL_NODE] = self.op.nodes + [self.op.remote_node]
7825 def CheckPrereq(self):
7828 def Exec(self, feedback_fn):
7829 if self.op.remote_node is not None:
7831 for node in self.op.nodes:
7832 instances.extend(_GetNodeSecondaryInstances(self.cfg, node))
7835 if i.primary_node == self.op.remote_node:
7836 raise errors.OpPrereqError("Node %s is the primary node of"
7837 " instance %s, cannot use it as"
7839 (self.op.remote_node, i.name),
7841 result.append([i.name, self.op.remote_node])
7843 ial = IAllocator(self.cfg, self.rpc,
7844 mode=constants.IALLOCATOR_MODE_MEVAC,
7845 evac_nodes=self.op.nodes)
7846 ial.Run(self.op.iallocator, validate=True)
7848 raise errors.OpExecError("No valid evacuation solution: %s" % ial.info,
7854 class LUGrowDisk(LogicalUnit):
7855 """Grow a disk of an instance.
7859 HTYPE = constants.HTYPE_INSTANCE
7860 _OP_REQP = ["instance_name", "disk", "amount", "wait_for_sync"]
7863 def ExpandNames(self):
7864 self._ExpandAndLockInstance()
7865 self.needed_locks[locking.LEVEL_NODE] = []
7866 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7868 def DeclareLocks(self, level):
7869 if level == locking.LEVEL_NODE:
7870 self._LockInstancesNodes()
7872 def BuildHooksEnv(self):
7875 This runs on the master, the primary and all the secondaries.
7879 "DISK": self.op.disk,
7880 "AMOUNT": self.op.amount,
7882 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7883 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7886 def CheckPrereq(self):
7887 """Check prerequisites.
7889 This checks that the instance is in the cluster.
7892 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7893 assert instance is not None, \
7894 "Cannot retrieve locked instance %s" % self.op.instance_name
7895 nodenames = list(instance.all_nodes)
7896 for node in nodenames:
7897 _CheckNodeOnline(self, node)
7900 self.instance = instance
7902 if instance.disk_template not in constants.DTS_GROWABLE:
7903 raise errors.OpPrereqError("Instance's disk layout does not support"
7904 " growing.", errors.ECODE_INVAL)
7906 self.disk = instance.FindDisk(self.op.disk)
7908 if instance.disk_template != constants.DT_FILE:
7909 # TODO: check the free disk space for file, when that feature will be
7911 _CheckNodesFreeDisk(self, nodenames, self.op.amount)
7913 def Exec(self, feedback_fn):
7914 """Execute disk grow.
7917 instance = self.instance
7919 for node in instance.all_nodes:
7920 self.cfg.SetDiskID(disk, node)
7921 result = self.rpc.call_blockdev_grow(node, disk, self.op.amount)
7922 result.Raise("Grow request failed to node %s" % node)
7924 # TODO: Rewrite code to work properly
7925 # DRBD goes into sync mode for a short amount of time after executing the
7926 # "resize" command. DRBD 8.x below version 8.0.13 contains a bug whereby
7927 # calling "resize" in sync mode fails. Sleeping for a short amount of
7928 # time is a work-around.
7931 disk.RecordGrow(self.op.amount)
7932 self.cfg.Update(instance, feedback_fn)
7933 if self.op.wait_for_sync:
7934 disk_abort = not _WaitForSync(self, instance)
7936 self.proc.LogWarning("Warning: disk sync-ing has not returned a good"
7937 " status.\nPlease check the instance.")
7940 class LUQueryInstanceData(NoHooksLU):
7941 """Query runtime instance data.
7944 _OP_REQP = ["instances", "static"]
7947 def ExpandNames(self):
7948 self.needed_locks = {}
7949 self.share_locks = dict.fromkeys(locking.LEVELS, 1)
7951 if not isinstance(self.op.instances, list):
7952 raise errors.OpPrereqError("Invalid argument type 'instances'",
7955 if self.op.instances:
7956 self.wanted_names = []
7957 for name in self.op.instances:
7958 full_name = _ExpandInstanceName(self.cfg, name)
7959 self.wanted_names.append(full_name)
7960 self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
7962 self.wanted_names = None
7963 self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
7965 self.needed_locks[locking.LEVEL_NODE] = []
7966 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7968 def DeclareLocks(self, level):
7969 if level == locking.LEVEL_NODE:
7970 self._LockInstancesNodes()
7972 def CheckPrereq(self):
7973 """Check prerequisites.
7975 This only checks the optional instance list against the existing names.
7978 if self.wanted_names is None:
7979 self.wanted_names = self.acquired_locks[locking.LEVEL_INSTANCE]
7981 self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name
7982 in self.wanted_names]
7985 def _ComputeBlockdevStatus(self, node, instance_name, dev):
7986 """Returns the status of a block device
7989 if self.op.static or not node:
7992 self.cfg.SetDiskID(dev, node)
7994 result = self.rpc.call_blockdev_find(node, dev)
7998 result.Raise("Can't compute disk status for %s" % instance_name)
8000 status = result.payload
8004 return (status.dev_path, status.major, status.minor,
8005 status.sync_percent, status.estimated_time,
8006 status.is_degraded, status.ldisk_status)
8008 def _ComputeDiskStatus(self, instance, snode, dev):
8009 """Compute block device status.
8012 if dev.dev_type in constants.LDS_DRBD:
8013 # we change the snode then (otherwise we use the one passed in)
8014 if dev.logical_id[0] == instance.primary_node:
8015 snode = dev.logical_id[1]
8017 snode = dev.logical_id[0]
8019 dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
8021 dev_sstatus = self._ComputeBlockdevStatus(snode, instance.name, dev)
8024 dev_children = [self._ComputeDiskStatus(instance, snode, child)
8025 for child in dev.children]
8030 "iv_name": dev.iv_name,
8031 "dev_type": dev.dev_type,
8032 "logical_id": dev.logical_id,
8033 "physical_id": dev.physical_id,
8034 "pstatus": dev_pstatus,
8035 "sstatus": dev_sstatus,
8036 "children": dev_children,
8043 def Exec(self, feedback_fn):
8044 """Gather and return data"""
8047 cluster = self.cfg.GetClusterInfo()
8049 for instance in self.wanted_instances:
8050 if not self.op.static:
8051 remote_info = self.rpc.call_instance_info(instance.primary_node,
8053 instance.hypervisor)
8054 remote_info.Raise("Error checking node %s" % instance.primary_node)
8055 remote_info = remote_info.payload
8056 if remote_info and "state" in remote_info:
8059 remote_state = "down"
8062 if instance.admin_up:
8065 config_state = "down"
8067 disks = [self._ComputeDiskStatus(instance, None, device)
8068 for device in instance.disks]
8071 "name": instance.name,
8072 "config_state": config_state,
8073 "run_state": remote_state,
8074 "pnode": instance.primary_node,
8075 "snodes": instance.secondary_nodes,
8077 # this happens to be the same format used for hooks
8078 "nics": _NICListToTuple(self, instance.nics),
8079 "disk_template": instance.disk_template,
8081 "hypervisor": instance.hypervisor,
8082 "network_port": instance.network_port,
8083 "hv_instance": instance.hvparams,
8084 "hv_actual": cluster.FillHV(instance, skip_globals=True),
8085 "be_instance": instance.beparams,
8086 "be_actual": cluster.FillBE(instance),
8087 "serial_no": instance.serial_no,
8088 "mtime": instance.mtime,
8089 "ctime": instance.ctime,
8090 "uuid": instance.uuid,
8093 result[instance.name] = idict
8098 class LUSetInstanceParams(LogicalUnit):
8099 """Modifies an instances's parameters.
8102 HPATH = "instance-modify"
8103 HTYPE = constants.HTYPE_INSTANCE
8104 _OP_REQP = ["instance_name"]
8107 def CheckArguments(self):
8108 if not hasattr(self.op, 'nics'):
8110 if not hasattr(self.op, 'disks'):
8112 if not hasattr(self.op, 'beparams'):
8113 self.op.beparams = {}
8114 if not hasattr(self.op, 'hvparams'):
8115 self.op.hvparams = {}
8116 if not hasattr(self.op, "disk_template"):
8117 self.op.disk_template = None
8118 if not hasattr(self.op, "remote_node"):
8119 self.op.remote_node = None
8120 if not hasattr(self.op, "os_name"):
8121 self.op.os_name = None
8122 if not hasattr(self.op, "force_variant"):
8123 self.op.force_variant = False
8124 self.op.force = getattr(self.op, "force", False)
8125 if not (self.op.nics or self.op.disks or self.op.disk_template or
8126 self.op.hvparams or self.op.beparams or self.op.os_name):
8127 raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
8129 if self.op.hvparams:
8130 _CheckGlobalHvParams(self.op.hvparams)
8134 for disk_op, disk_dict in self.op.disks:
8135 if disk_op == constants.DDM_REMOVE:
8138 elif disk_op == constants.DDM_ADD:
8141 if not isinstance(disk_op, int):
8142 raise errors.OpPrereqError("Invalid disk index", errors.ECODE_INVAL)
8143 if not isinstance(disk_dict, dict):
8144 msg = "Invalid disk value: expected dict, got '%s'" % disk_dict
8145 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
8147 if disk_op == constants.DDM_ADD:
8148 mode = disk_dict.setdefault('mode', constants.DISK_RDWR)
8149 if mode not in constants.DISK_ACCESS_SET:
8150 raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
8152 size = disk_dict.get('size', None)
8154 raise errors.OpPrereqError("Required disk parameter size missing",
8158 except (TypeError, ValueError), err:
8159 raise errors.OpPrereqError("Invalid disk size parameter: %s" %
8160 str(err), errors.ECODE_INVAL)
8161 disk_dict['size'] = size
8163 # modification of disk
8164 if 'size' in disk_dict:
8165 raise errors.OpPrereqError("Disk size change not possible, use"
8166 " grow-disk", errors.ECODE_INVAL)
8168 if disk_addremove > 1:
8169 raise errors.OpPrereqError("Only one disk add or remove operation"
8170 " supported at a time", errors.ECODE_INVAL)
8172 if self.op.disks and self.op.disk_template is not None:
8173 raise errors.OpPrereqError("Disk template conversion and other disk"
8174 " changes not supported at the same time",
8177 if self.op.disk_template:
8178 _CheckDiskTemplate(self.op.disk_template)
8179 if (self.op.disk_template in constants.DTS_NET_MIRROR and
8180 self.op.remote_node is None):
8181 raise errors.OpPrereqError("Changing the disk template to a mirrored"
8182 " one requires specifying a secondary node",
8187 for nic_op, nic_dict in self.op.nics:
8188 if nic_op == constants.DDM_REMOVE:
8191 elif nic_op == constants.DDM_ADD:
8194 if not isinstance(nic_op, int):
8195 raise errors.OpPrereqError("Invalid nic index", errors.ECODE_INVAL)
8196 if not isinstance(nic_dict, dict):
8197 msg = "Invalid nic value: expected dict, got '%s'" % nic_dict
8198 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
8200 # nic_dict should be a dict
8201 nic_ip = nic_dict.get('ip', None)
8202 if nic_ip is not None:
8203 if nic_ip.lower() == constants.VALUE_NONE:
8204 nic_dict['ip'] = None
8206 if not utils.IsValidIP(nic_ip):
8207 raise errors.OpPrereqError("Invalid IP address '%s'" % nic_ip,
8210 nic_bridge = nic_dict.get('bridge', None)
8211 nic_link = nic_dict.get('link', None)
8212 if nic_bridge and nic_link:
8213 raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
8214 " at the same time", errors.ECODE_INVAL)
8215 elif nic_bridge and nic_bridge.lower() == constants.VALUE_NONE:
8216 nic_dict['bridge'] = None
8217 elif nic_link and nic_link.lower() == constants.VALUE_NONE:
8218 nic_dict['link'] = None
8220 if nic_op == constants.DDM_ADD:
8221 nic_mac = nic_dict.get('mac', None)
8223 nic_dict['mac'] = constants.VALUE_AUTO
8225 if 'mac' in nic_dict:
8226 nic_mac = nic_dict['mac']
8227 if nic_mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
8228 nic_mac = utils.NormalizeAndValidateMac(nic_mac)
8230 if nic_op != constants.DDM_ADD and nic_mac == constants.VALUE_AUTO:
8231 raise errors.OpPrereqError("'auto' is not a valid MAC address when"
8232 " modifying an existing nic",
8235 if nic_addremove > 1:
8236 raise errors.OpPrereqError("Only one NIC add or remove operation"
8237 " supported at a time", errors.ECODE_INVAL)
8239 def ExpandNames(self):
8240 self._ExpandAndLockInstance()
8241 self.needed_locks[locking.LEVEL_NODE] = []
8242 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
8244 def DeclareLocks(self, level):
8245 if level == locking.LEVEL_NODE:
8246 self._LockInstancesNodes()
8247 if self.op.disk_template and self.op.remote_node:
8248 self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
8249 self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
8251 def BuildHooksEnv(self):
8254 This runs on the master, primary and secondaries.
8258 if constants.BE_MEMORY in self.be_new:
8259 args['memory'] = self.be_new[constants.BE_MEMORY]
8260 if constants.BE_VCPUS in self.be_new:
8261 args['vcpus'] = self.be_new[constants.BE_VCPUS]
8262 # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
8263 # information at all.
8266 nic_override = dict(self.op.nics)
8267 c_nicparams = self.cluster.nicparams[constants.PP_DEFAULT]
8268 for idx, nic in enumerate(self.instance.nics):
8269 if idx in nic_override:
8270 this_nic_override = nic_override[idx]
8272 this_nic_override = {}
8273 if 'ip' in this_nic_override:
8274 ip = this_nic_override['ip']
8277 if 'mac' in this_nic_override:
8278 mac = this_nic_override['mac']
8281 if idx in self.nic_pnew:
8282 nicparams = self.nic_pnew[idx]
8284 nicparams = objects.FillDict(c_nicparams, nic.nicparams)
8285 mode = nicparams[constants.NIC_MODE]
8286 link = nicparams[constants.NIC_LINK]
8287 args['nics'].append((ip, mac, mode, link))
8288 if constants.DDM_ADD in nic_override:
8289 ip = nic_override[constants.DDM_ADD].get('ip', None)
8290 mac = nic_override[constants.DDM_ADD]['mac']
8291 nicparams = self.nic_pnew[constants.DDM_ADD]
8292 mode = nicparams[constants.NIC_MODE]
8293 link = nicparams[constants.NIC_LINK]
8294 args['nics'].append((ip, mac, mode, link))
8295 elif constants.DDM_REMOVE in nic_override:
8296 del args['nics'][-1]
8298 env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
8299 if self.op.disk_template:
8300 env["NEW_DISK_TEMPLATE"] = self.op.disk_template
8301 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
8305 def _GetUpdatedParams(old_params, update_dict,
8306 default_values, parameter_types):
8307 """Return the new params dict for the given params.
8309 @type old_params: dict
8310 @param old_params: old parameters
8311 @type update_dict: dict
8312 @param update_dict: dict containing new parameter values,
8313 or constants.VALUE_DEFAULT to reset the
8314 parameter to its default value
8315 @type default_values: dict
8316 @param default_values: default values for the filled parameters
8317 @type parameter_types: dict
8318 @param parameter_types: dict mapping target dict keys to types
8319 in constants.ENFORCEABLE_TYPES
8320 @rtype: (dict, dict)
8321 @return: (new_parameters, filled_parameters)
8324 params_copy = copy.deepcopy(old_params)
8325 for key, val in update_dict.iteritems():
8326 if val == constants.VALUE_DEFAULT:
8328 del params_copy[key]
8332 params_copy[key] = val
8333 utils.ForceDictType(params_copy, parameter_types)
8334 params_filled = objects.FillDict(default_values, params_copy)
8335 return (params_copy, params_filled)
8337 def CheckPrereq(self):
8338 """Check prerequisites.
8340 This only checks the instance list against the existing names.
8343 self.force = self.op.force
8345 # checking the new params on the primary/secondary nodes
8347 instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
8348 cluster = self.cluster = self.cfg.GetClusterInfo()
8349 assert self.instance is not None, \
8350 "Cannot retrieve locked instance %s" % self.op.instance_name
8351 pnode = instance.primary_node
8352 nodelist = list(instance.all_nodes)
8354 if self.op.disk_template:
8355 if instance.disk_template == self.op.disk_template:
8356 raise errors.OpPrereqError("Instance already has disk template %s" %
8357 instance.disk_template, errors.ECODE_INVAL)
8359 if (instance.disk_template,
8360 self.op.disk_template) not in self._DISK_CONVERSIONS:
8361 raise errors.OpPrereqError("Unsupported disk template conversion from"
8362 " %s to %s" % (instance.disk_template,
8363 self.op.disk_template),
8365 if self.op.disk_template in constants.DTS_NET_MIRROR:
8366 _CheckNodeOnline(self, self.op.remote_node)
8367 _CheckNodeNotDrained(self, self.op.remote_node)
8368 disks = [{"size": d.size} for d in instance.disks]
8369 required = _ComputeDiskSize(self.op.disk_template, disks)
8370 _CheckNodesFreeDisk(self, [self.op.remote_node], required)
8371 _CheckInstanceDown(self, instance, "cannot change disk template")
8373 # hvparams processing
8374 if self.op.hvparams:
8375 i_hvdict, hv_new = self._GetUpdatedParams(
8376 instance.hvparams, self.op.hvparams,
8377 cluster.hvparams[instance.hypervisor],
8378 constants.HVS_PARAMETER_TYPES)
8380 hypervisor.GetHypervisor(
8381 instance.hypervisor).CheckParameterSyntax(hv_new)
8382 _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
8383 self.hv_new = hv_new # the new actual values
8384 self.hv_inst = i_hvdict # the new dict (without defaults)
8386 self.hv_new = self.hv_inst = {}
8388 # beparams processing
8389 if self.op.beparams:
8390 i_bedict, be_new = self._GetUpdatedParams(
8391 instance.beparams, self.op.beparams,
8392 cluster.beparams[constants.PP_DEFAULT],
8393 constants.BES_PARAMETER_TYPES)
8394 self.be_new = be_new # the new actual values
8395 self.be_inst = i_bedict # the new dict (without defaults)
8397 self.be_new = self.be_inst = {}
8401 if constants.BE_MEMORY in self.op.beparams and not self.force:
8402 mem_check_list = [pnode]
8403 if be_new[constants.BE_AUTO_BALANCE]:
8404 # either we changed auto_balance to yes or it was from before
8405 mem_check_list.extend(instance.secondary_nodes)
8406 instance_info = self.rpc.call_instance_info(pnode, instance.name,
8407 instance.hypervisor)
8408 nodeinfo = self.rpc.call_node_info(mem_check_list, self.cfg.GetVGName(),
8409 instance.hypervisor)
8410 pninfo = nodeinfo[pnode]
8411 msg = pninfo.fail_msg
8413 # Assume the primary node is unreachable and go ahead
8414 self.warn.append("Can't get info from primary node %s: %s" %
8416 elif not isinstance(pninfo.payload.get('memory_free', None), int):
8417 self.warn.append("Node data from primary node %s doesn't contain"
8418 " free memory information" % pnode)
8419 elif instance_info.fail_msg:
8420 self.warn.append("Can't get instance runtime information: %s" %
8421 instance_info.fail_msg)
8423 if instance_info.payload:
8424 current_mem = int(instance_info.payload['memory'])
8426 # Assume instance not running
8427 # (there is a slight race condition here, but it's not very probable,
8428 # and we have no other way to check)
8430 miss_mem = (be_new[constants.BE_MEMORY] - current_mem -
8431 pninfo.payload['memory_free'])
8433 raise errors.OpPrereqError("This change will prevent the instance"
8434 " from starting, due to %d MB of memory"
8435 " missing on its primary node" % miss_mem,
8438 if be_new[constants.BE_AUTO_BALANCE]:
8439 for node, nres in nodeinfo.items():
8440 if node not in instance.secondary_nodes:
8444 self.warn.append("Can't get info from secondary node %s: %s" %
8446 elif not isinstance(nres.payload.get('memory_free', None), int):
8447 self.warn.append("Secondary node %s didn't return free"
8448 " memory information" % node)
8449 elif be_new[constants.BE_MEMORY] > nres.payload['memory_free']:
8450 self.warn.append("Not enough memory to failover instance to"
8451 " secondary node %s" % node)
8456 for nic_op, nic_dict in self.op.nics:
8457 if nic_op == constants.DDM_REMOVE:
8458 if not instance.nics:
8459 raise errors.OpPrereqError("Instance has no NICs, cannot remove",
8462 if nic_op != constants.DDM_ADD:
8464 if not instance.nics:
8465 raise errors.OpPrereqError("Invalid NIC index %s, instance has"
8466 " no NICs" % nic_op,
8468 if nic_op < 0 or nic_op >= len(instance.nics):
8469 raise errors.OpPrereqError("Invalid NIC index %s, valid values"
8471 (nic_op, len(instance.nics) - 1),
8473 old_nic_params = instance.nics[nic_op].nicparams
8474 old_nic_ip = instance.nics[nic_op].ip
8479 update_params_dict = dict([(key, nic_dict[key])
8480 for key in constants.NICS_PARAMETERS
8481 if key in nic_dict])
8483 if 'bridge' in nic_dict:
8484 update_params_dict[constants.NIC_LINK] = nic_dict['bridge']
8486 new_nic_params, new_filled_nic_params = \
8487 self._GetUpdatedParams(old_nic_params, update_params_dict,
8488 cluster.nicparams[constants.PP_DEFAULT],
8489 constants.NICS_PARAMETER_TYPES)
8490 objects.NIC.CheckParameterSyntax(new_filled_nic_params)
8491 self.nic_pinst[nic_op] = new_nic_params
8492 self.nic_pnew[nic_op] = new_filled_nic_params
8493 new_nic_mode = new_filled_nic_params[constants.NIC_MODE]
8495 if new_nic_mode == constants.NIC_MODE_BRIDGED:
8496 nic_bridge = new_filled_nic_params[constants.NIC_LINK]
8497 msg = self.rpc.call_bridges_exist(pnode, [nic_bridge]).fail_msg
8499 msg = "Error checking bridges on node %s: %s" % (pnode, msg)
8501 self.warn.append(msg)
8503 raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
8504 if new_nic_mode == constants.NIC_MODE_ROUTED:
8505 if 'ip' in nic_dict:
8506 nic_ip = nic_dict['ip']
8510 raise errors.OpPrereqError('Cannot set the nic ip to None'
8511 ' on a routed nic', errors.ECODE_INVAL)
8512 if 'mac' in nic_dict:
8513 nic_mac = nic_dict['mac']
8515 raise errors.OpPrereqError('Cannot set the nic mac to None',
8517 elif nic_mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
8518 # otherwise generate the mac
8519 nic_dict['mac'] = self.cfg.GenerateMAC(self.proc.GetECId())
8521 # or validate/reserve the current one
8523 self.cfg.ReserveMAC(nic_mac, self.proc.GetECId())
8524 except errors.ReservationError:
8525 raise errors.OpPrereqError("MAC address %s already in use"
8526 " in cluster" % nic_mac,
8527 errors.ECODE_NOTUNIQUE)
8530 if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
8531 raise errors.OpPrereqError("Disk operations not supported for"
8532 " diskless instances",
8534 for disk_op, _ in self.op.disks:
8535 if disk_op == constants.DDM_REMOVE:
8536 if len(instance.disks) == 1:
8537 raise errors.OpPrereqError("Cannot remove the last disk of"
8538 " an instance", errors.ECODE_INVAL)
8539 _CheckInstanceDown(self, instance, "cannot remove disks")
8541 if (disk_op == constants.DDM_ADD and
8542 len(instance.nics) >= constants.MAX_DISKS):
8543 raise errors.OpPrereqError("Instance has too many disks (%d), cannot"
8544 " add more" % constants.MAX_DISKS,
8546 if disk_op not in (constants.DDM_ADD, constants.DDM_REMOVE):
8548 if disk_op < 0 or disk_op >= len(instance.disks):
8549 raise errors.OpPrereqError("Invalid disk index %s, valid values"
8551 (disk_op, len(instance.disks)),
8555 if self.op.os_name and not self.op.force:
8556 _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
8557 self.op.force_variant)
8561 def _ConvertPlainToDrbd(self, feedback_fn):
8562 """Converts an instance from plain to drbd.
8565 feedback_fn("Converting template to drbd")
8566 instance = self.instance
8567 pnode = instance.primary_node
8568 snode = self.op.remote_node
8570 # create a fake disk info for _GenerateDiskTemplate
8571 disk_info = [{"size": d.size, "mode": d.mode} for d in instance.disks]
8572 new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
8573 instance.name, pnode, [snode],
8574 disk_info, None, None, 0)
8575 info = _GetInstanceInfoText(instance)
8576 feedback_fn("Creating aditional volumes...")
8577 # first, create the missing data and meta devices
8578 for disk in new_disks:
8579 # unfortunately this is... not too nice
8580 _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
8582 for child in disk.children:
8583 _CreateSingleBlockDev(self, snode, instance, child, info, True)
8584 # at this stage, all new LVs have been created, we can rename the
8586 feedback_fn("Renaming original volumes...")
8587 rename_list = [(o, n.children[0].logical_id)
8588 for (o, n) in zip(instance.disks, new_disks)]
8589 result = self.rpc.call_blockdev_rename(pnode, rename_list)
8590 result.Raise("Failed to rename original LVs")
8592 feedback_fn("Initializing DRBD devices...")
8593 # all child devices are in place, we can now create the DRBD devices
8594 for disk in new_disks:
8595 for node in [pnode, snode]:
8596 f_create = node == pnode
8597 _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
8599 # at this point, the instance has been modified
8600 instance.disk_template = constants.DT_DRBD8
8601 instance.disks = new_disks
8602 self.cfg.Update(instance, feedback_fn)
8604 # disks are created, waiting for sync
8605 disk_abort = not _WaitForSync(self, instance)
8607 raise errors.OpExecError("There are some degraded disks for"
8608 " this instance, please cleanup manually")
8610 def _ConvertDrbdToPlain(self, feedback_fn):
8611 """Converts an instance from drbd to plain.
8614 instance = self.instance
8615 assert len(instance.secondary_nodes) == 1
8616 pnode = instance.primary_node
8617 snode = instance.secondary_nodes[0]
8618 feedback_fn("Converting template to plain")
8620 old_disks = instance.disks
8621 new_disks = [d.children[0] for d in old_disks]
8623 # copy over size and mode
8624 for parent, child in zip(old_disks, new_disks):
8625 child.size = parent.size
8626 child.mode = parent.mode
8628 # update instance structure
8629 instance.disks = new_disks
8630 instance.disk_template = constants.DT_PLAIN
8631 self.cfg.Update(instance, feedback_fn)
8633 feedback_fn("Removing volumes on the secondary node...")
8634 for disk in old_disks:
8635 self.cfg.SetDiskID(disk, snode)
8636 msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
8638 self.LogWarning("Could not remove block device %s on node %s,"
8639 " continuing anyway: %s", disk.iv_name, snode, msg)
8641 feedback_fn("Removing unneeded volumes on the primary node...")
8642 for idx, disk in enumerate(old_disks):
8643 meta = disk.children[1]
8644 self.cfg.SetDiskID(meta, pnode)
8645 msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
8647 self.LogWarning("Could not remove metadata for disk %d on node %s,"
8648 " continuing anyway: %s", idx, pnode, msg)
8651 def Exec(self, feedback_fn):
8652 """Modifies an instance.
8654 All parameters take effect only at the next restart of the instance.
8657 # Process here the warnings from CheckPrereq, as we don't have a
8658 # feedback_fn there.
8659 for warn in self.warn:
8660 feedback_fn("WARNING: %s" % warn)
8663 instance = self.instance
8665 for disk_op, disk_dict in self.op.disks:
8666 if disk_op == constants.DDM_REMOVE:
8667 # remove the last disk
8668 device = instance.disks.pop()
8669 device_idx = len(instance.disks)
8670 for node, disk in device.ComputeNodeTree(instance.primary_node):
8671 self.cfg.SetDiskID(disk, node)
8672 msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
8674 self.LogWarning("Could not remove disk/%d on node %s: %s,"
8675 " continuing anyway", device_idx, node, msg)
8676 result.append(("disk/%d" % device_idx, "remove"))
8677 elif disk_op == constants.DDM_ADD:
8679 if instance.disk_template == constants.DT_FILE:
8680 file_driver, file_path = instance.disks[0].logical_id
8681 file_path = os.path.dirname(file_path)
8683 file_driver = file_path = None
8684 disk_idx_base = len(instance.disks)
8685 new_disk = _GenerateDiskTemplate(self,
8686 instance.disk_template,
8687 instance.name, instance.primary_node,
8688 instance.secondary_nodes,
8693 instance.disks.append(new_disk)
8694 info = _GetInstanceInfoText(instance)
8696 logging.info("Creating volume %s for instance %s",
8697 new_disk.iv_name, instance.name)
8698 # Note: this needs to be kept in sync with _CreateDisks
8700 for node in instance.all_nodes:
8701 f_create = node == instance.primary_node
8703 _CreateBlockDev(self, node, instance, new_disk,
8704 f_create, info, f_create)
8705 except errors.OpExecError, err:
8706 self.LogWarning("Failed to create volume %s (%s) on"
8708 new_disk.iv_name, new_disk, node, err)
8709 result.append(("disk/%d" % disk_idx_base, "add:size=%s,mode=%s" %
8710 (new_disk.size, new_disk.mode)))
8712 # change a given disk
8713 instance.disks[disk_op].mode = disk_dict['mode']
8714 result.append(("disk.mode/%d" % disk_op, disk_dict['mode']))
8716 if self.op.disk_template:
8717 r_shut = _ShutdownInstanceDisks(self, instance)
8719 raise errors.OpExecError("Cannot shutdow instance disks, unable to"
8720 " proceed with disk template conversion")
8721 mode = (instance.disk_template, self.op.disk_template)
8723 self._DISK_CONVERSIONS[mode](self, feedback_fn)
8725 self.cfg.ReleaseDRBDMinors(instance.name)
8727 result.append(("disk_template", self.op.disk_template))
8730 for nic_op, nic_dict in self.op.nics:
8731 if nic_op == constants.DDM_REMOVE:
8732 # remove the last nic
8733 del instance.nics[-1]
8734 result.append(("nic.%d" % len(instance.nics), "remove"))
8735 elif nic_op == constants.DDM_ADD:
8736 # mac and bridge should be set, by now
8737 mac = nic_dict['mac']
8738 ip = nic_dict.get('ip', None)
8739 nicparams = self.nic_pinst[constants.DDM_ADD]
8740 new_nic = objects.NIC(mac=mac, ip=ip, nicparams=nicparams)
8741 instance.nics.append(new_nic)
8742 result.append(("nic.%d" % (len(instance.nics) - 1),
8743 "add:mac=%s,ip=%s,mode=%s,link=%s" %
8744 (new_nic.mac, new_nic.ip,
8745 self.nic_pnew[constants.DDM_ADD][constants.NIC_MODE],
8746 self.nic_pnew[constants.DDM_ADD][constants.NIC_LINK]
8749 for key in 'mac', 'ip':
8751 setattr(instance.nics[nic_op], key, nic_dict[key])
8752 if nic_op in self.nic_pinst:
8753 instance.nics[nic_op].nicparams = self.nic_pinst[nic_op]
8754 for key, val in nic_dict.iteritems():
8755 result.append(("nic.%s/%d" % (key, nic_op), val))
8758 if self.op.hvparams:
8759 instance.hvparams = self.hv_inst
8760 for key, val in self.op.hvparams.iteritems():
8761 result.append(("hv/%s" % key, val))
8764 if self.op.beparams:
8765 instance.beparams = self.be_inst
8766 for key, val in self.op.beparams.iteritems():
8767 result.append(("be/%s" % key, val))
8771 instance.os = self.op.os_name
8773 self.cfg.Update(instance, feedback_fn)
8777 _DISK_CONVERSIONS = {
8778 (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
8779 (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
8783 class LUQueryExports(NoHooksLU):
8784 """Query the exports list
8787 _OP_REQP = ['nodes']
8790 def ExpandNames(self):
8791 self.needed_locks = {}
8792 self.share_locks[locking.LEVEL_NODE] = 1
8793 if not self.op.nodes:
8794 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
8796 self.needed_locks[locking.LEVEL_NODE] = \
8797 _GetWantedNodes(self, self.op.nodes)
8799 def CheckPrereq(self):
8800 """Check prerequisites.
8803 self.nodes = self.acquired_locks[locking.LEVEL_NODE]
8805 def Exec(self, feedback_fn):
8806 """Compute the list of all the exported system images.
8809 @return: a dictionary with the structure node->(export-list)
8810 where export-list is a list of the instances exported on
8814 rpcresult = self.rpc.call_export_list(self.nodes)
8816 for node in rpcresult:
8817 if rpcresult[node].fail_msg:
8818 result[node] = False
8820 result[node] = rpcresult[node].payload
8825 class LUExportInstance(LogicalUnit):
8826 """Export an instance to an image in the cluster.
8829 HPATH = "instance-export"
8830 HTYPE = constants.HTYPE_INSTANCE
8831 _OP_REQP = ["instance_name", "target_node", "shutdown"]
8834 def CheckArguments(self):
8835 """Check the arguments.
8838 _CheckBooleanOpField(self.op, "remove_instance")
8839 _CheckBooleanOpField(self.op, "ignore_remove_failures")
8841 self.shutdown_timeout = getattr(self.op, "shutdown_timeout",
8842 constants.DEFAULT_SHUTDOWN_TIMEOUT)
8843 self.remove_instance = getattr(self.op, "remove_instance", False)
8844 self.ignore_remove_failures = getattr(self.op, "ignore_remove_failures",
8847 if self.remove_instance and not self.op.shutdown:
8848 raise errors.OpPrereqError("Can not remove instance without shutting it"
8851 def ExpandNames(self):
8852 self._ExpandAndLockInstance()
8854 # FIXME: lock only instance primary and destination node
8856 # Sad but true, for now we have do lock all nodes, as we don't know where
8857 # the previous export might be, and and in this LU we search for it and
8858 # remove it from its current node. In the future we could fix this by:
8859 # - making a tasklet to search (share-lock all), then create the new one,
8860 # then one to remove, after
8861 # - removing the removal operation altogether
8862 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
8864 def DeclareLocks(self, level):
8865 """Last minute lock declaration."""
8866 # All nodes are locked anyway, so nothing to do here.
8868 def BuildHooksEnv(self):
8871 This will run on the master, primary node and target node.
8875 "EXPORT_NODE": self.op.target_node,
8876 "EXPORT_DO_SHUTDOWN": self.op.shutdown,
8877 "SHUTDOWN_TIMEOUT": self.shutdown_timeout,
8878 # TODO: Generic function for boolean env variables
8879 "REMOVE_INSTANCE": str(bool(self.remove_instance)),
8881 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
8882 nl = [self.cfg.GetMasterNode(), self.instance.primary_node,
8883 self.op.target_node]
8886 def CheckPrereq(self):
8887 """Check prerequisites.
8889 This checks that the instance and node names are valid.
8892 instance_name = self.op.instance_name
8893 self.instance = self.cfg.GetInstanceInfo(instance_name)
8894 assert self.instance is not None, \
8895 "Cannot retrieve locked instance %s" % self.op.instance_name
8896 _CheckNodeOnline(self, self.instance.primary_node)
8898 self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
8899 self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
8900 assert self.dst_node is not None
8902 _CheckNodeOnline(self, self.dst_node.name)
8903 _CheckNodeNotDrained(self, self.dst_node.name)
8905 # instance disk type verification
8906 # TODO: Implement export support for file-based disks
8907 for disk in self.instance.disks:
8908 if disk.dev_type == constants.LD_FILE:
8909 raise errors.OpPrereqError("Export not supported for instances with"
8910 " file-based disks", errors.ECODE_INVAL)
8912 def _CleanupExports(self, feedback_fn):
8913 """Removes exports of current instance from all other nodes.
8915 If an instance in a cluster with nodes A..D was exported to node C, its
8916 exports will be removed from the nodes A, B and D.
8919 nodelist = self.cfg.GetNodeList()
8920 nodelist.remove(self.dst_node.name)
8922 # on one-node clusters nodelist will be empty after the removal
8923 # if we proceed the backup would be removed because OpQueryExports
8924 # substitutes an empty list with the full cluster node list.
8925 iname = self.instance.name
8927 feedback_fn("Removing old exports for instance %s" % iname)
8928 exportlist = self.rpc.call_export_list(nodelist)
8929 for node in exportlist:
8930 if exportlist[node].fail_msg:
8932 if iname in exportlist[node].payload:
8933 msg = self.rpc.call_export_remove(node, iname).fail_msg
8935 self.LogWarning("Could not remove older export for instance %s"
8936 " on node %s: %s", iname, node, msg)
8938 def Exec(self, feedback_fn):
8939 """Export an instance to an image in the cluster.
8942 instance = self.instance
8943 src_node = instance.primary_node
8945 if self.op.shutdown:
8946 # shutdown the instance, but not the disks
8947 feedback_fn("Shutting down instance %s" % instance.name)
8948 result = self.rpc.call_instance_shutdown(src_node, instance,
8949 self.shutdown_timeout)
8950 # TODO: Maybe ignore failures if ignore_remove_failures is set
8951 result.Raise("Could not shutdown instance %s on"
8952 " node %s" % (instance.name, src_node))
8954 # set the disks ID correctly since call_instance_start needs the
8955 # correct drbd minor to create the symlinks
8956 for disk in instance.disks:
8957 self.cfg.SetDiskID(disk, src_node)
8959 activate_disks = (not instance.admin_up)
8962 # Activate the instance disks if we'exporting a stopped instance
8963 feedback_fn("Activating disks for %s" % instance.name)
8964 _StartInstanceDisks(self, instance, None)
8967 helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
8970 helper.CreateSnapshots()
8972 (fin_resu, dresults) = helper.LocalExport(self.dst_node)
8976 # Check for backwards compatibility
8977 assert len(dresults) == len(instance.disks)
8978 assert compat.all(isinstance(i, bool) for i in dresults), \
8979 "Not all results are boolean: %r" % dresults
8983 feedback_fn("Deactivating disks for %s" % instance.name)
8984 _ShutdownInstanceDisks(self, instance)
8986 # Remove instance if requested
8987 if self.remove_instance:
8988 if not (compat.all(dresults) and fin_resu):
8989 feedback_fn("Not removing instance %s as parts of the export failed" %
8992 feedback_fn("Removing instance %s" % instance.name)
8993 _RemoveInstance(self, feedback_fn, instance,
8994 self.ignore_remove_failures)
8996 self._CleanupExports(feedback_fn)
8998 return fin_resu, dresults
9001 class LURemoveExport(NoHooksLU):
9002 """Remove exports related to the named instance.
9005 _OP_REQP = ["instance_name"]
9008 def ExpandNames(self):
9009 self.needed_locks = {}
9010 # We need all nodes to be locked in order for RemoveExport to work, but we
9011 # don't need to lock the instance itself, as nothing will happen to it (and
9012 # we can remove exports also for a removed instance)
9013 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9015 def CheckPrereq(self):
9016 """Check prerequisites.
9020 def Exec(self, feedback_fn):
9021 """Remove any export.
9024 instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
9025 # If the instance was not found we'll try with the name that was passed in.
9026 # This will only work if it was an FQDN, though.
9028 if not instance_name:
9030 instance_name = self.op.instance_name
9032 locked_nodes = self.acquired_locks[locking.LEVEL_NODE]
9033 exportlist = self.rpc.call_export_list(locked_nodes)
9035 for node in exportlist:
9036 msg = exportlist[node].fail_msg
9038 self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
9040 if instance_name in exportlist[node].payload:
9042 result = self.rpc.call_export_remove(node, instance_name)
9043 msg = result.fail_msg
9045 logging.error("Could not remove export for instance %s"
9046 " on node %s: %s", instance_name, node, msg)
9048 if fqdn_warn and not found:
9049 feedback_fn("Export not found. If trying to remove an export belonging"
9050 " to a deleted instance please use its Fully Qualified"
9054 class TagsLU(NoHooksLU): # pylint: disable-msg=W0223
9057 This is an abstract class which is the parent of all the other tags LUs.
9061 def ExpandNames(self):
9062 self.needed_locks = {}
9063 if self.op.kind == constants.TAG_NODE:
9064 self.op.name = _ExpandNodeName(self.cfg, self.op.name)
9065 self.needed_locks[locking.LEVEL_NODE] = self.op.name
9066 elif self.op.kind == constants.TAG_INSTANCE:
9067 self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
9068 self.needed_locks[locking.LEVEL_INSTANCE] = self.op.name
9070 def CheckPrereq(self):
9071 """Check prerequisites.
9074 if self.op.kind == constants.TAG_CLUSTER:
9075 self.target = self.cfg.GetClusterInfo()
9076 elif self.op.kind == constants.TAG_NODE:
9077 self.target = self.cfg.GetNodeInfo(self.op.name)
9078 elif self.op.kind == constants.TAG_INSTANCE:
9079 self.target = self.cfg.GetInstanceInfo(self.op.name)
9081 raise errors.OpPrereqError("Wrong tag type requested (%s)" %
9082 str(self.op.kind), errors.ECODE_INVAL)
9085 class LUGetTags(TagsLU):
9086 """Returns the tags of a given object.
9089 _OP_REQP = ["kind", "name"]
9092 def Exec(self, feedback_fn):
9093 """Returns the tag list.
9096 return list(self.target.GetTags())
9099 class LUSearchTags(NoHooksLU):
9100 """Searches the tags for a given pattern.
9103 _OP_REQP = ["pattern"]
9106 def ExpandNames(self):
9107 self.needed_locks = {}
9109 def CheckPrereq(self):
9110 """Check prerequisites.
9112 This checks the pattern passed for validity by compiling it.
9116 self.re = re.compile(self.op.pattern)
9117 except re.error, err:
9118 raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
9119 (self.op.pattern, err), errors.ECODE_INVAL)
9121 def Exec(self, feedback_fn):
9122 """Returns the tag list.
9126 tgts = [("/cluster", cfg.GetClusterInfo())]
9127 ilist = cfg.GetAllInstancesInfo().values()
9128 tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
9129 nlist = cfg.GetAllNodesInfo().values()
9130 tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
9132 for path, target in tgts:
9133 for tag in target.GetTags():
9134 if self.re.search(tag):
9135 results.append((path, tag))
9139 class LUAddTags(TagsLU):
9140 """Sets a tag on a given object.
9143 _OP_REQP = ["kind", "name", "tags"]
9146 def CheckPrereq(self):
9147 """Check prerequisites.
9149 This checks the type and length of the tag name and value.
9152 TagsLU.CheckPrereq(self)
9153 for tag in self.op.tags:
9154 objects.TaggableObject.ValidateTag(tag)
9156 def Exec(self, feedback_fn):
9161 for tag in self.op.tags:
9162 self.target.AddTag(tag)
9163 except errors.TagError, err:
9164 raise errors.OpExecError("Error while setting tag: %s" % str(err))
9165 self.cfg.Update(self.target, feedback_fn)
9168 class LUDelTags(TagsLU):
9169 """Delete a list of tags from a given object.
9172 _OP_REQP = ["kind", "name", "tags"]
9175 def CheckPrereq(self):
9176 """Check prerequisites.
9178 This checks that we have the given tag.
9181 TagsLU.CheckPrereq(self)
9182 for tag in self.op.tags:
9183 objects.TaggableObject.ValidateTag(tag)
9184 del_tags = frozenset(self.op.tags)
9185 cur_tags = self.target.GetTags()
9186 if not del_tags <= cur_tags:
9187 diff_tags = del_tags - cur_tags
9188 diff_names = ["'%s'" % tag for tag in diff_tags]
9190 raise errors.OpPrereqError("Tag(s) %s not found" %
9191 (",".join(diff_names)), errors.ECODE_NOENT)
9193 def Exec(self, feedback_fn):
9194 """Remove the tag from the object.
9197 for tag in self.op.tags:
9198 self.target.RemoveTag(tag)
9199 self.cfg.Update(self.target, feedback_fn)
9202 class LUTestDelay(NoHooksLU):
9203 """Sleep for a specified amount of time.
9205 This LU sleeps on the master and/or nodes for a specified amount of
9209 _OP_REQP = ["duration", "on_master", "on_nodes"]
9212 def ExpandNames(self):
9213 """Expand names and set required locks.
9215 This expands the node list, if any.
9218 self.needed_locks = {}
9219 if self.op.on_nodes:
9220 # _GetWantedNodes can be used here, but is not always appropriate to use
9221 # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
9223 self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
9224 self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
9226 def CheckPrereq(self):
9227 """Check prerequisites.
9231 def Exec(self, feedback_fn):
9232 """Do the actual sleep.
9235 if self.op.on_master:
9236 if not utils.TestDelay(self.op.duration):
9237 raise errors.OpExecError("Error during master delay test")
9238 if self.op.on_nodes:
9239 result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
9240 for node, node_result in result.items():
9241 node_result.Raise("Failure during rpc call to node %s" % node)
9244 class IAllocator(object):
9245 """IAllocator framework.
9247 An IAllocator instance has three sets of attributes:
9248 - cfg that is needed to query the cluster
9249 - input data (all members of the _KEYS class attribute are required)
9250 - four buffer attributes (in|out_data|text), that represent the
9251 input (to the external script) in text and data structure format,
9252 and the output from it, again in two formats
9253 - the result variables from the script (success, info, nodes) for
9257 # pylint: disable-msg=R0902
9258 # lots of instance attributes
9260 "name", "mem_size", "disks", "disk_template",
9261 "os", "tags", "nics", "vcpus", "hypervisor",
9264 "name", "relocate_from",
9270 def __init__(self, cfg, rpc, mode, **kwargs):
9273 # init buffer variables
9274 self.in_text = self.out_text = self.in_data = self.out_data = None
9275 # init all input fields so that pylint is happy
9277 self.mem_size = self.disks = self.disk_template = None
9278 self.os = self.tags = self.nics = self.vcpus = None
9279 self.hypervisor = None
9280 self.relocate_from = None
9282 self.evac_nodes = None
9284 self.required_nodes = None
9285 # init result fields
9286 self.success = self.info = self.result = None
9287 if self.mode == constants.IALLOCATOR_MODE_ALLOC:
9288 keyset = self._ALLO_KEYS
9289 fn = self._AddNewInstance
9290 elif self.mode == constants.IALLOCATOR_MODE_RELOC:
9291 keyset = self._RELO_KEYS
9292 fn = self._AddRelocateInstance
9293 elif self.mode == constants.IALLOCATOR_MODE_MEVAC:
9294 keyset = self._EVAC_KEYS
9295 fn = self._AddEvacuateNodes
9297 raise errors.ProgrammerError("Unknown mode '%s' passed to the"
9298 " IAllocator" % self.mode)
9300 if key not in keyset:
9301 raise errors.ProgrammerError("Invalid input parameter '%s' to"
9302 " IAllocator" % key)
9303 setattr(self, key, kwargs[key])
9306 if key not in kwargs:
9307 raise errors.ProgrammerError("Missing input parameter '%s' to"
9308 " IAllocator" % key)
9309 self._BuildInputData(fn)
9311 def _ComputeClusterData(self):
9312 """Compute the generic allocator input data.
9314 This is the data that is independent of the actual operation.
9318 cluster_info = cfg.GetClusterInfo()
9321 "version": constants.IALLOCATOR_VERSION,
9322 "cluster_name": cfg.GetClusterName(),
9323 "cluster_tags": list(cluster_info.GetTags()),
9324 "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
9325 # we don't have job IDs
9327 iinfo = cfg.GetAllInstancesInfo().values()
9328 i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
9332 node_list = cfg.GetNodeList()
9334 if self.mode == constants.IALLOCATOR_MODE_ALLOC:
9335 hypervisor_name = self.hypervisor
9336 elif self.mode == constants.IALLOCATOR_MODE_RELOC:
9337 hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
9338 elif self.mode == constants.IALLOCATOR_MODE_MEVAC:
9339 hypervisor_name = cluster_info.enabled_hypervisors[0]
9341 node_data = self.rpc.call_node_info(node_list, cfg.GetVGName(),
9344 self.rpc.call_all_instances_info(node_list,
9345 cluster_info.enabled_hypervisors)
9346 for nname, nresult in node_data.items():
9347 # first fill in static (config-based) values
9348 ninfo = cfg.GetNodeInfo(nname)
9350 "tags": list(ninfo.GetTags()),
9351 "primary_ip": ninfo.primary_ip,
9352 "secondary_ip": ninfo.secondary_ip,
9353 "offline": ninfo.offline,
9354 "drained": ninfo.drained,
9355 "master_candidate": ninfo.master_candidate,
9358 if not (ninfo.offline or ninfo.drained):
9359 nresult.Raise("Can't get data for node %s" % nname)
9360 node_iinfo[nname].Raise("Can't get node instance info from node %s" %
9362 remote_info = nresult.payload
9364 for attr in ['memory_total', 'memory_free', 'memory_dom0',
9365 'vg_size', 'vg_free', 'cpu_total']:
9366 if attr not in remote_info:
9367 raise errors.OpExecError("Node '%s' didn't return attribute"
9368 " '%s'" % (nname, attr))
9369 if not isinstance(remote_info[attr], int):
9370 raise errors.OpExecError("Node '%s' returned invalid value"
9372 (nname, attr, remote_info[attr]))
9373 # compute memory used by primary instances
9374 i_p_mem = i_p_up_mem = 0
9375 for iinfo, beinfo in i_list:
9376 if iinfo.primary_node == nname:
9377 i_p_mem += beinfo[constants.BE_MEMORY]
9378 if iinfo.name not in node_iinfo[nname].payload:
9381 i_used_mem = int(node_iinfo[nname].payload[iinfo.name]['memory'])
9382 i_mem_diff = beinfo[constants.BE_MEMORY] - i_used_mem
9383 remote_info['memory_free'] -= max(0, i_mem_diff)
9386 i_p_up_mem += beinfo[constants.BE_MEMORY]
9388 # compute memory used by instances
9390 "total_memory": remote_info['memory_total'],
9391 "reserved_memory": remote_info['memory_dom0'],
9392 "free_memory": remote_info['memory_free'],
9393 "total_disk": remote_info['vg_size'],
9394 "free_disk": remote_info['vg_free'],
9395 "total_cpus": remote_info['cpu_total'],
9396 "i_pri_memory": i_p_mem,
9397 "i_pri_up_memory": i_p_up_mem,
9401 node_results[nname] = pnr
9402 data["nodes"] = node_results
9406 for iinfo, beinfo in i_list:
9408 for nic in iinfo.nics:
9409 filled_params = objects.FillDict(
9410 cluster_info.nicparams[constants.PP_DEFAULT],
9412 nic_dict = {"mac": nic.mac,
9414 "mode": filled_params[constants.NIC_MODE],
9415 "link": filled_params[constants.NIC_LINK],
9417 if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
9418 nic_dict["bridge"] = filled_params[constants.NIC_LINK]
9419 nic_data.append(nic_dict)
9421 "tags": list(iinfo.GetTags()),
9422 "admin_up": iinfo.admin_up,
9423 "vcpus": beinfo[constants.BE_VCPUS],
9424 "memory": beinfo[constants.BE_MEMORY],
9426 "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
9428 "disks": [{"size": dsk.size, "mode": dsk.mode} for dsk in iinfo.disks],
9429 "disk_template": iinfo.disk_template,
9430 "hypervisor": iinfo.hypervisor,
9432 pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
9434 instance_data[iinfo.name] = pir
9436 data["instances"] = instance_data
9440 def _AddNewInstance(self):
9441 """Add new instance data to allocator structure.
9443 This in combination with _AllocatorGetClusterData will create the
9444 correct structure needed as input for the allocator.
9446 The checks for the completeness of the opcode must have already been
9450 disk_space = _ComputeDiskSize(self.disk_template, self.disks)
9452 if self.disk_template in constants.DTS_NET_MIRROR:
9453 self.required_nodes = 2
9455 self.required_nodes = 1
9458 "disk_template": self.disk_template,
9461 "vcpus": self.vcpus,
9462 "memory": self.mem_size,
9463 "disks": self.disks,
9464 "disk_space_total": disk_space,
9466 "required_nodes": self.required_nodes,
9470 def _AddRelocateInstance(self):
9471 """Add relocate instance data to allocator structure.
9473 This in combination with _IAllocatorGetClusterData will create the
9474 correct structure needed as input for the allocator.
9476 The checks for the completeness of the opcode must have already been
9480 instance = self.cfg.GetInstanceInfo(self.name)
9481 if instance is None:
9482 raise errors.ProgrammerError("Unknown instance '%s' passed to"
9483 " IAllocator" % self.name)
9485 if instance.disk_template not in constants.DTS_NET_MIRROR:
9486 raise errors.OpPrereqError("Can't relocate non-mirrored instances",
9489 if len(instance.secondary_nodes) != 1:
9490 raise errors.OpPrereqError("Instance has not exactly one secondary node",
9493 self.required_nodes = 1
9494 disk_sizes = [{'size': disk.size} for disk in instance.disks]
9495 disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
9499 "disk_space_total": disk_space,
9500 "required_nodes": self.required_nodes,
9501 "relocate_from": self.relocate_from,
9505 def _AddEvacuateNodes(self):
9506 """Add evacuate nodes data to allocator structure.
9510 "evac_nodes": self.evac_nodes
9514 def _BuildInputData(self, fn):
9515 """Build input data structures.
9518 self._ComputeClusterData()
9521 request["type"] = self.mode
9522 self.in_data["request"] = request
9524 self.in_text = serializer.Dump(self.in_data)
9526 def Run(self, name, validate=True, call_fn=None):
9527 """Run an instance allocator and return the results.
9531 call_fn = self.rpc.call_iallocator_runner
9533 result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
9534 result.Raise("Failure while running the iallocator script")
9536 self.out_text = result.payload
9538 self._ValidateResult()
9540 def _ValidateResult(self):
9541 """Process the allocator results.
9543 This will process and if successful save the result in
9544 self.out_data and the other parameters.
9548 rdict = serializer.Load(self.out_text)
9549 except Exception, err:
9550 raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
9552 if not isinstance(rdict, dict):
9553 raise errors.OpExecError("Can't parse iallocator results: not a dict")
9555 # TODO: remove backwards compatiblity in later versions
9556 if "nodes" in rdict and "result" not in rdict:
9557 rdict["result"] = rdict["nodes"]
9560 for key in "success", "info", "result":
9561 if key not in rdict:
9562 raise errors.OpExecError("Can't parse iallocator results:"
9563 " missing key '%s'" % key)
9564 setattr(self, key, rdict[key])
9566 if not isinstance(rdict["result"], list):
9567 raise errors.OpExecError("Can't parse iallocator results: 'result' key"
9569 self.out_data = rdict
9572 class LUTestAllocator(NoHooksLU):
9573 """Run allocator tests.
9575 This LU runs the allocator tests
9578 _OP_REQP = ["direction", "mode", "name"]
9580 def CheckPrereq(self):
9581 """Check prerequisites.
9583 This checks the opcode parameters depending on the director and mode test.
9586 if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
9587 for attr in ["name", "mem_size", "disks", "disk_template",
9588 "os", "tags", "nics", "vcpus"]:
9589 if not hasattr(self.op, attr):
9590 raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
9591 attr, errors.ECODE_INVAL)
9592 iname = self.cfg.ExpandInstanceName(self.op.name)
9593 if iname is not None:
9594 raise errors.OpPrereqError("Instance '%s' already in the cluster" %
9595 iname, errors.ECODE_EXISTS)
9596 if not isinstance(self.op.nics, list):
9597 raise errors.OpPrereqError("Invalid parameter 'nics'",
9599 for row in self.op.nics:
9600 if (not isinstance(row, dict) or
9603 "bridge" not in row):
9604 raise errors.OpPrereqError("Invalid contents of the 'nics'"
9605 " parameter", errors.ECODE_INVAL)
9606 if not isinstance(self.op.disks, list):
9607 raise errors.OpPrereqError("Invalid parameter 'disks'",
9609 for row in self.op.disks:
9610 if (not isinstance(row, dict) or
9611 "size" not in row or
9612 not isinstance(row["size"], int) or
9613 "mode" not in row or
9614 row["mode"] not in ['r', 'w']):
9615 raise errors.OpPrereqError("Invalid contents of the 'disks'"
9616 " parameter", errors.ECODE_INVAL)
9617 if not hasattr(self.op, "hypervisor") or self.op.hypervisor is None:
9618 self.op.hypervisor = self.cfg.GetHypervisorType()
9619 elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
9620 if not hasattr(self.op, "name"):
9621 raise errors.OpPrereqError("Missing attribute 'name' on opcode input",
9623 fname = _ExpandInstanceName(self.cfg, self.op.name)
9624 self.op.name = fname
9625 self.relocate_from = self.cfg.GetInstanceInfo(fname).secondary_nodes
9626 elif self.op.mode == constants.IALLOCATOR_MODE_MEVAC:
9627 if not hasattr(self.op, "evac_nodes"):
9628 raise errors.OpPrereqError("Missing attribute 'evac_nodes' on"
9629 " opcode input", errors.ECODE_INVAL)
9631 raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
9632 self.op.mode, errors.ECODE_INVAL)
9634 if self.op.direction == constants.IALLOCATOR_DIR_OUT:
9635 if not hasattr(self.op, "allocator") or self.op.allocator is None:
9636 raise errors.OpPrereqError("Missing allocator name",
9638 elif self.op.direction != constants.IALLOCATOR_DIR_IN:
9639 raise errors.OpPrereqError("Wrong allocator test '%s'" %
9640 self.op.direction, errors.ECODE_INVAL)
9642 def Exec(self, feedback_fn):
9643 """Run the allocator test.
9646 if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
9647 ial = IAllocator(self.cfg, self.rpc,
9650 mem_size=self.op.mem_size,
9651 disks=self.op.disks,
9652 disk_template=self.op.disk_template,
9656 vcpus=self.op.vcpus,
9657 hypervisor=self.op.hypervisor,
9659 elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
9660 ial = IAllocator(self.cfg, self.rpc,
9663 relocate_from=list(self.relocate_from),
9665 elif self.op.mode == constants.IALLOCATOR_MODE_MEVAC:
9666 ial = IAllocator(self.cfg, self.rpc,
9668 evac_nodes=self.op.evac_nodes)
9670 raise errors.ProgrammerError("Uncatched mode %s in"
9671 " LUTestAllocator.Exec", self.op.mode)
9673 if self.op.direction == constants.IALLOCATOR_DIR_IN:
9674 result = ial.in_text
9676 ial.Run(self.op.allocator, validate=False)
9677 result = ial.out_text