4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 """Logical units dealing with instances."""
29 from ganeti import compat
30 from ganeti import constants
31 from ganeti import errors
33 from ganeti import hypervisor
34 from ganeti import locking
35 from ganeti.masterd import iallocator
36 from ganeti import masterd
37 from ganeti import netutils
38 from ganeti import objects
39 from ganeti import opcodes
40 from ganeti import pathutils
41 from ganeti import rpc
42 from ganeti import utils
44 from ganeti.cmdlib.base import NoHooksLU, LogicalUnit, ResultWithJobs
46 from ganeti.cmdlib.common import INSTANCE_DOWN, \
47 INSTANCE_NOT_RUNNING, CAN_CHANGE_INSTANCE_OFFLINE, CheckNodeOnline, \
48 ShareAll, GetDefaultIAllocator, CheckInstanceNodeGroups, \
49 LoadNodeEvacResult, CheckIAllocatorOrNode, CheckParamsNotGlobal, \
50 IsExclusiveStorageEnabledNode, CheckHVParams, CheckOSParams, \
51 AnnotateDiskParams, GetUpdatedParams, ExpandInstanceName, \
52 ComputeIPolicySpecViolation, CheckInstanceState, ExpandNodeName
53 from ganeti.cmdlib.instance_storage import CreateDisks, \
54 CheckNodesFreeDiskPerVG, WipeDisks, WaitForSync, \
55 IsExclusiveStorageEnabledNodeName, CreateSingleBlockDev, ComputeDisks, \
56 CheckRADOSFreeSpace, ComputeDiskSizePerVG, GenerateDiskTemplate, \
57 CreateBlockDev, StartInstanceDisks, ShutdownInstanceDisks, \
59 from ganeti.cmdlib.instance_utils import BuildInstanceHookEnvByObject, \
60 GetClusterDomainSecret, BuildInstanceHookEnv, NICListToTuple, \
61 NICToTuple, CheckNodeNotDrained, RemoveInstance, CopyLockList, \
62 ReleaseLocks, CheckNodeVmCapable, CheckTargetNodeIPolicy, \
63 GetInstanceInfoText, RemoveDisks, CheckNodeFreeMemory, \
64 CheckInstanceBridgesExist, CheckNicsBridgesExist, CheckNodeHasOS
66 import ganeti.masterd.instance
69 #: Type description for changes as returned by L{_ApplyContainerMods}'s
71 _TApplyContModsCbChanges = \
72 ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
78 def _CheckHostnameSane(lu, name):
79 """Ensures that a given hostname resolves to a 'sane' name.
81 The given name is required to be a prefix of the resolved hostname,
82 to prevent accidental mismatches.
84 @param lu: the logical unit on behalf of which we're checking
85 @param name: the name we should resolve and check
86 @return: the resolved hostname object
89 hostname = netutils.GetHostname(name=name)
90 if hostname.name != name:
91 lu.LogInfo("Resolved given name '%s' to '%s'", name, hostname.name)
92 if not utils.MatchNameComponent(name, [hostname.name]):
93 raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
94 " same as given hostname '%s'") %
95 (hostname.name, name), errors.ECODE_INVAL)
99 def _CheckOpportunisticLocking(op):
100 """Generate error if opportunistic locking is not possible.
103 if op.opportunistic_locking and not op.iallocator:
104 raise errors.OpPrereqError("Opportunistic locking is only available in"
105 " combination with an instance allocator",
109 def _CreateInstanceAllocRequest(op, disks, nics, beparams, node_whitelist):
110 """Wrapper around IAReqInstanceAlloc.
112 @param op: The instance opcode
113 @param disks: The computed disks
114 @param nics: The computed nics
115 @param beparams: The full filled beparams
116 @param node_whitelist: List of nodes which should appear as online to the
117 allocator (unless the node is already marked offline)
119 @returns: A filled L{iallocator.IAReqInstanceAlloc}
122 spindle_use = beparams[constants.BE_SPINDLE_USE]
123 return iallocator.IAReqInstanceAlloc(name=op.instance_name,
124 disk_template=op.disk_template,
127 vcpus=beparams[constants.BE_VCPUS],
128 memory=beparams[constants.BE_MAXMEM],
129 spindle_use=spindle_use,
131 nics=[n.ToDict() for n in nics],
132 hypervisor=op.hypervisor,
133 node_whitelist=node_whitelist)
136 def _ComputeFullBeParams(op, cluster):
137 """Computes the full beparams.
139 @param op: The instance opcode
140 @param cluster: The cluster config object
142 @return: The fully filled beparams
145 default_beparams = cluster.beparams[constants.PP_DEFAULT]
146 for param, value in op.beparams.iteritems():
147 if value == constants.VALUE_AUTO:
148 op.beparams[param] = default_beparams[param]
149 objects.UpgradeBeParams(op.beparams)
150 utils.ForceDictType(op.beparams, constants.BES_PARAMETER_TYPES)
151 return cluster.SimpleFillBE(op.beparams)
154 def _ComputeNics(op, cluster, default_ip, cfg, ec_id):
155 """Computes the nics.
157 @param op: The instance opcode
158 @param cluster: Cluster configuration object
159 @param default_ip: The default ip to assign
160 @param cfg: An instance of the configuration object
161 @param ec_id: Execution context ID
163 @returns: The build up nics
168 nic_mode_req = nic.get(constants.INIC_MODE, None)
169 nic_mode = nic_mode_req
170 if nic_mode is None or nic_mode == constants.VALUE_AUTO:
171 nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
173 net = nic.get(constants.INIC_NETWORK, None)
174 link = nic.get(constants.NIC_LINK, None)
175 ip = nic.get(constants.INIC_IP, None)
177 if net is None or net.lower() == constants.VALUE_NONE:
180 if nic_mode_req is not None or link is not None:
181 raise errors.OpPrereqError("If network is given, no mode or link"
182 " is allowed to be passed",
186 if ip is None or ip.lower() == constants.VALUE_NONE:
188 elif ip.lower() == constants.VALUE_AUTO:
189 if not op.name_check:
190 raise errors.OpPrereqError("IP address set to auto but name checks"
191 " have been skipped",
195 # We defer pool operations until later, so that the iallocator has
196 # filled in the instance's node(s) dimara
197 if ip.lower() == constants.NIC_IP_POOL:
199 raise errors.OpPrereqError("if ip=pool, parameter network"
200 " must be passed too",
203 elif not netutils.IPAddress.IsValid(ip):
204 raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
209 # TODO: check the ip address for uniqueness
210 if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
211 raise errors.OpPrereqError("Routed nic mode requires an ip address",
214 # MAC address verification
215 mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
216 if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
217 mac = utils.NormalizeAndValidateMac(mac)
220 # TODO: We need to factor this out
221 cfg.ReserveMAC(mac, ec_id)
222 except errors.ReservationError:
223 raise errors.OpPrereqError("MAC address %s already in use"
225 errors.ECODE_NOTUNIQUE)
227 # Build nic parameters
230 nicparams[constants.NIC_MODE] = nic_mode
232 nicparams[constants.NIC_LINK] = link
234 check_params = cluster.SimpleFillNIC(nicparams)
235 objects.NIC.CheckParameterSyntax(check_params)
236 net_uuid = cfg.LookupNetwork(net)
237 name = nic.get(constants.INIC_NAME, None)
238 if name is not None and name.lower() == constants.VALUE_NONE:
240 nic_obj = objects.NIC(mac=mac, ip=nic_ip, name=name,
241 network=net_uuid, nicparams=nicparams)
242 nic_obj.uuid = cfg.GenerateUniqueID(ec_id)
248 def _CheckForConflictingIp(lu, ip, node):
249 """In case of conflicting IP address raise error.
252 @param ip: IP address
254 @param node: node name
257 (conf_net, _) = lu.cfg.CheckIPInNodeGroup(ip, node)
258 if conf_net is not None:
259 raise errors.OpPrereqError(("The requested IP address (%s) belongs to"
260 " network %s, but the target NIC does not." %
267 def _ComputeIPolicyInstanceSpecViolation(
268 ipolicy, instance_spec, disk_template,
269 _compute_fn=ComputeIPolicySpecViolation):
270 """Compute if instance specs meets the specs of ipolicy.
273 @param ipolicy: The ipolicy to verify against
274 @param instance_spec: dict
275 @param instance_spec: The instance spec to verify
276 @type disk_template: string
277 @param disk_template: the disk template of the instance
278 @param _compute_fn: The function to verify ipolicy (unittest only)
279 @see: L{ComputeIPolicySpecViolation}
282 mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
283 cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
284 disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
285 disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
286 nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
287 spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
289 return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
290 disk_sizes, spindle_use, disk_template)
293 def _CheckOSVariant(os_obj, name):
294 """Check whether an OS name conforms to the os variants specification.
296 @type os_obj: L{objects.OS}
297 @param os_obj: OS object to check
299 @param name: OS name passed by the user, to check for validity
302 variant = objects.OS.GetVariant(name)
303 if not os_obj.supported_variants:
305 raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
306 " passed)" % (os_obj.name, variant),
310 raise errors.OpPrereqError("OS name must include a variant",
313 if variant not in os_obj.supported_variants:
314 raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
317 class LUInstanceCreate(LogicalUnit):
318 """Create an instance.
321 HPATH = "instance-add"
322 HTYPE = constants.HTYPE_INSTANCE
325 def CheckArguments(self):
329 # do not require name_check to ease forward/backward compatibility
331 if self.op.no_install and self.op.start:
332 self.LogInfo("No-installation mode selected, disabling startup")
333 self.op.start = False
334 # validate/normalize the instance name
335 self.op.instance_name = \
336 netutils.Hostname.GetNormalizedName(self.op.instance_name)
338 if self.op.ip_check and not self.op.name_check:
339 # TODO: make the ip check more flexible and not depend on the name check
340 raise errors.OpPrereqError("Cannot do IP address check without a name"
341 " check", errors.ECODE_INVAL)
343 # check nics' parameter names
344 for nic in self.op.nics:
345 utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
346 # check that NIC's parameters names are unique and valid
347 utils.ValidateDeviceNames("NIC", self.op.nics)
349 # check that disk's names are unique and valid
350 utils.ValidateDeviceNames("disk", self.op.disks)
352 cluster = self.cfg.GetClusterInfo()
353 if not self.op.disk_template in cluster.enabled_disk_templates:
354 raise errors.OpPrereqError("Cannot create an instance with disk template"
355 " '%s', because it is not enabled in the"
356 " cluster. Enabled disk templates are: %s." %
357 (self.op.disk_template,
358 ",".join(cluster.enabled_disk_templates)))
360 # check disks. parameter names and consistent adopt/no-adopt strategy
361 has_adopt = has_no_adopt = False
362 for disk in self.op.disks:
363 if self.op.disk_template != constants.DT_EXT:
364 utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
365 if constants.IDISK_ADOPT in disk:
369 if has_adopt and has_no_adopt:
370 raise errors.OpPrereqError("Either all disks are adopted or none is",
373 if self.op.disk_template not in constants.DTS_MAY_ADOPT:
374 raise errors.OpPrereqError("Disk adoption is not supported for the"
375 " '%s' disk template" %
376 self.op.disk_template,
378 if self.op.iallocator is not None:
379 raise errors.OpPrereqError("Disk adoption not allowed with an"
380 " iallocator script", errors.ECODE_INVAL)
381 if self.op.mode == constants.INSTANCE_IMPORT:
382 raise errors.OpPrereqError("Disk adoption not allowed for"
383 " instance import", errors.ECODE_INVAL)
385 if self.op.disk_template in constants.DTS_MUST_ADOPT:
386 raise errors.OpPrereqError("Disk template %s requires disk adoption,"
387 " but no 'adopt' parameter given" %
388 self.op.disk_template,
391 self.adopt_disks = has_adopt
393 # instance name verification
394 if self.op.name_check:
395 self.hostname1 = _CheckHostnameSane(self, self.op.instance_name)
396 self.op.instance_name = self.hostname1.name
397 # used in CheckPrereq for ip ping check
398 self.check_ip = self.hostname1.ip
402 # file storage checks
403 if (self.op.file_driver and
404 not self.op.file_driver in constants.FILE_DRIVER):
405 raise errors.OpPrereqError("Invalid file driver name '%s'" %
406 self.op.file_driver, errors.ECODE_INVAL)
408 if self.op.disk_template == constants.DT_FILE:
409 opcodes.RequireFileStorage()
410 elif self.op.disk_template == constants.DT_SHARED_FILE:
411 opcodes.RequireSharedFileStorage()
413 ### Node/iallocator related checks
414 CheckIAllocatorOrNode(self, "iallocator", "pnode")
416 if self.op.pnode is not None:
417 if self.op.disk_template in constants.DTS_INT_MIRROR:
418 if self.op.snode is None:
419 raise errors.OpPrereqError("The networked disk templates need"
420 " a mirror node", errors.ECODE_INVAL)
422 self.LogWarning("Secondary node will be ignored on non-mirrored disk"
426 _CheckOpportunisticLocking(self.op)
428 self._cds = GetClusterDomainSecret()
430 if self.op.mode == constants.INSTANCE_IMPORT:
431 # On import force_variant must be True, because if we forced it at
432 # initial install, our only chance when importing it back is that it
434 self.op.force_variant = True
436 if self.op.no_install:
437 self.LogInfo("No-installation mode has no effect during import")
439 elif self.op.mode == constants.INSTANCE_CREATE:
440 if self.op.os_type is None:
441 raise errors.OpPrereqError("No guest OS specified",
443 if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
444 raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
445 " installation" % self.op.os_type,
447 if self.op.disk_template is None:
448 raise errors.OpPrereqError("No disk template specified",
451 elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
452 # Check handshake to ensure both clusters have the same domain secret
453 src_handshake = self.op.source_handshake
454 if not src_handshake:
455 raise errors.OpPrereqError("Missing source handshake",
458 errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
461 raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
464 # Load and check source CA
465 self.source_x509_ca_pem = self.op.source_x509_ca
466 if not self.source_x509_ca_pem:
467 raise errors.OpPrereqError("Missing source X509 CA",
471 (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
473 except OpenSSL.crypto.Error, err:
474 raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
475 (err, ), errors.ECODE_INVAL)
477 (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
478 if errcode is not None:
479 raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
482 self.source_x509_ca = cert
484 src_instance_name = self.op.source_instance_name
485 if not src_instance_name:
486 raise errors.OpPrereqError("Missing source instance name",
489 self.source_instance_name = \
490 netutils.GetHostname(name=src_instance_name).name
493 raise errors.OpPrereqError("Invalid instance creation mode %r" %
494 self.op.mode, errors.ECODE_INVAL)
496 def ExpandNames(self):
497 """ExpandNames for CreateInstance.
499 Figure out the right locks for instance creation.
502 self.needed_locks = {}
504 instance_name = self.op.instance_name
505 # this is just a preventive check, but someone might still add this
506 # instance in the meantime, and creation will fail at lock-add time
507 if instance_name in self.cfg.GetInstanceList():
508 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
509 instance_name, errors.ECODE_EXISTS)
511 self.add_locks[locking.LEVEL_INSTANCE] = instance_name
513 if self.op.iallocator:
514 # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
515 # specifying a group on instance creation and then selecting nodes from
517 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
518 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
520 if self.op.opportunistic_locking:
521 self.opportunistic_locks[locking.LEVEL_NODE] = True
522 self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
524 self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
525 nodelist = [self.op.pnode]
526 if self.op.snode is not None:
527 self.op.snode = ExpandNodeName(self.cfg, self.op.snode)
528 nodelist.append(self.op.snode)
529 self.needed_locks[locking.LEVEL_NODE] = nodelist
531 # in case of import lock the source node too
532 if self.op.mode == constants.INSTANCE_IMPORT:
533 src_node = self.op.src_node
534 src_path = self.op.src_path
537 self.op.src_path = src_path = self.op.instance_name
540 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
541 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
542 self.op.src_node = None
543 if os.path.isabs(src_path):
544 raise errors.OpPrereqError("Importing an instance from a path"
545 " requires a source node option",
548 self.op.src_node = src_node = ExpandNodeName(self.cfg, src_node)
549 if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
550 self.needed_locks[locking.LEVEL_NODE].append(src_node)
551 if not os.path.isabs(src_path):
552 self.op.src_path = src_path = \
553 utils.PathJoin(pathutils.EXPORT_DIR, src_path)
555 self.needed_locks[locking.LEVEL_NODE_RES] = \
556 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
558 def _RunAllocator(self):
559 """Run the allocator based on input opcode.
562 if self.op.opportunistic_locking:
563 # Only consider nodes for which a lock is held
564 node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
566 node_whitelist = None
568 #TODO Export network to iallocator so that it chooses a pnode
569 # in a nodegroup that has the desired network connected to
570 req = _CreateInstanceAllocRequest(self.op, self.disks,
571 self.nics, self.be_full,
573 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
575 ial.Run(self.op.iallocator)
578 # When opportunistic locks are used only a temporary failure is generated
579 if self.op.opportunistic_locking:
580 ecode = errors.ECODE_TEMP_NORES
582 ecode = errors.ECODE_NORES
584 raise errors.OpPrereqError("Can't compute nodes using"
585 " iallocator '%s': %s" %
586 (self.op.iallocator, ial.info),
589 self.op.pnode = ial.result[0]
590 self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
591 self.op.instance_name, self.op.iallocator,
592 utils.CommaJoin(ial.result))
594 assert req.RequiredNodes() in (1, 2), "Wrong node count from iallocator"
596 if req.RequiredNodes() == 2:
597 self.op.snode = ial.result[1]
599 def BuildHooksEnv(self):
602 This runs on master, primary and secondary nodes of the instance.
606 "ADD_MODE": self.op.mode,
608 if self.op.mode == constants.INSTANCE_IMPORT:
609 env["SRC_NODE"] = self.op.src_node
610 env["SRC_PATH"] = self.op.src_path
611 env["SRC_IMAGES"] = self.src_images
613 env.update(BuildInstanceHookEnv(
614 name=self.op.instance_name,
615 primary_node=self.op.pnode,
616 secondary_nodes=self.secondaries,
617 status=self.op.start,
618 os_type=self.op.os_type,
619 minmem=self.be_full[constants.BE_MINMEM],
620 maxmem=self.be_full[constants.BE_MAXMEM],
621 vcpus=self.be_full[constants.BE_VCPUS],
622 nics=NICListToTuple(self, self.nics),
623 disk_template=self.op.disk_template,
624 disks=[(d[constants.IDISK_NAME], d[constants.IDISK_SIZE],
625 d[constants.IDISK_MODE]) for d in self.disks],
628 hypervisor_name=self.op.hypervisor,
634 def BuildHooksNodes(self):
635 """Build hooks nodes.
638 nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
641 def _ReadExportInfo(self):
642 """Reads the export information from disk.
644 It will override the opcode source node and path with the actual
645 information, if these two were not specified before.
647 @return: the export information
650 assert self.op.mode == constants.INSTANCE_IMPORT
652 src_node = self.op.src_node
653 src_path = self.op.src_path
656 locked_nodes = self.owned_locks(locking.LEVEL_NODE)
657 exp_list = self.rpc.call_export_list(locked_nodes)
659 for node in exp_list:
660 if exp_list[node].fail_msg:
662 if src_path in exp_list[node].payload:
664 self.op.src_node = src_node = node
665 self.op.src_path = src_path = utils.PathJoin(pathutils.EXPORT_DIR,
669 raise errors.OpPrereqError("No export found for relative path %s" %
670 src_path, errors.ECODE_INVAL)
672 CheckNodeOnline(self, src_node)
673 result = self.rpc.call_export_info(src_node, src_path)
674 result.Raise("No export or invalid export found in dir %s" % src_path)
676 export_info = objects.SerializableConfigParser.Loads(str(result.payload))
677 if not export_info.has_section(constants.INISECT_EXP):
678 raise errors.ProgrammerError("Corrupted export config",
679 errors.ECODE_ENVIRON)
681 ei_version = export_info.get(constants.INISECT_EXP, "version")
682 if (int(ei_version) != constants.EXPORT_VERSION):
683 raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
684 (ei_version, constants.EXPORT_VERSION),
685 errors.ECODE_ENVIRON)
688 def _ReadExportParams(self, einfo):
689 """Use export parameters as defaults.
691 In case the opcode doesn't specify (as in override) some instance
692 parameters, then try to use them from the export information, if
696 self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
698 if self.op.disk_template is None:
699 if einfo.has_option(constants.INISECT_INS, "disk_template"):
700 self.op.disk_template = einfo.get(constants.INISECT_INS,
702 if self.op.disk_template not in constants.DISK_TEMPLATES:
703 raise errors.OpPrereqError("Disk template specified in configuration"
704 " file is not one of the allowed values:"
706 " ".join(constants.DISK_TEMPLATES),
709 raise errors.OpPrereqError("No disk template specified and the export"
710 " is missing the disk_template information",
713 if not self.op.disks:
715 # TODO: import the disk iv_name too
716 for idx in range(constants.MAX_DISKS):
717 if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
718 disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
719 disks.append({constants.IDISK_SIZE: disk_sz})
720 self.op.disks = disks
721 if not disks and self.op.disk_template != constants.DT_DISKLESS:
722 raise errors.OpPrereqError("No disk info specified and the export"
723 " is missing the disk information",
728 for idx in range(constants.MAX_NICS):
729 if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
731 for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
732 v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
739 if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
740 self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
742 if (self.op.hypervisor is None and
743 einfo.has_option(constants.INISECT_INS, "hypervisor")):
744 self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
746 if einfo.has_section(constants.INISECT_HYP):
747 # use the export parameters but do not override the ones
748 # specified by the user
749 for name, value in einfo.items(constants.INISECT_HYP):
750 if name not in self.op.hvparams:
751 self.op.hvparams[name] = value
753 if einfo.has_section(constants.INISECT_BEP):
754 # use the parameters, without overriding
755 for name, value in einfo.items(constants.INISECT_BEP):
756 if name not in self.op.beparams:
757 self.op.beparams[name] = value
758 # Compatibility for the old "memory" be param
759 if name == constants.BE_MEMORY:
760 if constants.BE_MAXMEM not in self.op.beparams:
761 self.op.beparams[constants.BE_MAXMEM] = value
762 if constants.BE_MINMEM not in self.op.beparams:
763 self.op.beparams[constants.BE_MINMEM] = value
765 # try to read the parameters old style, from the main section
766 for name in constants.BES_PARAMETERS:
767 if (name not in self.op.beparams and
768 einfo.has_option(constants.INISECT_INS, name)):
769 self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
771 if einfo.has_section(constants.INISECT_OSP):
772 # use the parameters, without overriding
773 for name, value in einfo.items(constants.INISECT_OSP):
774 if name not in self.op.osparams:
775 self.op.osparams[name] = value
777 def _RevertToDefaults(self, cluster):
778 """Revert the instance parameters to the default values.
782 hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
783 for name in self.op.hvparams.keys():
784 if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
785 del self.op.hvparams[name]
787 be_defs = cluster.SimpleFillBE({})
788 for name in self.op.beparams.keys():
789 if name in be_defs and be_defs[name] == self.op.beparams[name]:
790 del self.op.beparams[name]
792 nic_defs = cluster.SimpleFillNIC({})
793 for nic in self.op.nics:
794 for name in constants.NICS_PARAMETERS:
795 if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
798 os_defs = cluster.SimpleFillOS(self.op.os_type, {})
799 for name in self.op.osparams.keys():
800 if name in os_defs and os_defs[name] == self.op.osparams[name]:
801 del self.op.osparams[name]
803 def _CalculateFileStorageDir(self):
804 """Calculate final instance file storage dir.
807 # file storage dir calculation/check
808 self.instance_file_storage_dir = None
809 if self.op.disk_template in constants.DTS_FILEBASED:
810 # build the full file storage dir path
813 if self.op.disk_template == constants.DT_SHARED_FILE:
814 get_fsd_fn = self.cfg.GetSharedFileStorageDir
816 get_fsd_fn = self.cfg.GetFileStorageDir
818 cfg_storagedir = get_fsd_fn()
819 if not cfg_storagedir:
820 raise errors.OpPrereqError("Cluster file storage dir not defined",
822 joinargs.append(cfg_storagedir)
824 if self.op.file_storage_dir is not None:
825 joinargs.append(self.op.file_storage_dir)
827 joinargs.append(self.op.instance_name)
829 # pylint: disable=W0142
830 self.instance_file_storage_dir = utils.PathJoin(*joinargs)
832 def CheckPrereq(self): # pylint: disable=R0914
833 """Check prerequisites.
836 self._CalculateFileStorageDir()
838 if self.op.mode == constants.INSTANCE_IMPORT:
839 export_info = self._ReadExportInfo()
840 self._ReadExportParams(export_info)
841 self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
843 self._old_instance_name = None
845 if (not self.cfg.GetVGName() and
846 self.op.disk_template not in constants.DTS_NOT_LVM):
847 raise errors.OpPrereqError("Cluster does not support lvm-based"
848 " instances", errors.ECODE_STATE)
850 if (self.op.hypervisor is None or
851 self.op.hypervisor == constants.VALUE_AUTO):
852 self.op.hypervisor = self.cfg.GetHypervisorType()
854 cluster = self.cfg.GetClusterInfo()
855 enabled_hvs = cluster.enabled_hypervisors
856 if self.op.hypervisor not in enabled_hvs:
857 raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
859 (self.op.hypervisor, ",".join(enabled_hvs)),
863 for tag in self.op.tags:
864 objects.TaggableObject.ValidateTag(tag)
866 # check hypervisor parameter syntax (locally)
867 utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
868 filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
870 hv_type = hypervisor.GetHypervisorClass(self.op.hypervisor)
871 hv_type.CheckParameterSyntax(filled_hvp)
872 self.hv_full = filled_hvp
873 # check that we don't specify global parameters on an instance
874 CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, "hypervisor",
875 "instance", "cluster")
877 # fill and remember the beparams dict
878 self.be_full = _ComputeFullBeParams(self.op, cluster)
880 # build os parameters
881 self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
883 # now that hvp/bep are in final format, let's reset to defaults,
885 if self.op.identify_defaults:
886 self._RevertToDefaults(cluster)
889 self.nics = _ComputeNics(self.op, cluster, self.check_ip, self.cfg,
892 # disk checks/pre-build
893 default_vg = self.cfg.GetVGName()
894 self.disks = ComputeDisks(self.op, default_vg)
896 if self.op.mode == constants.INSTANCE_IMPORT:
898 for idx in range(len(self.disks)):
899 option = "disk%d_dump" % idx
900 if export_info.has_option(constants.INISECT_INS, option):
901 # FIXME: are the old os-es, disk sizes, etc. useful?
902 export_name = export_info.get(constants.INISECT_INS, option)
903 image = utils.PathJoin(self.op.src_path, export_name)
904 disk_images.append(image)
906 disk_images.append(False)
908 self.src_images = disk_images
910 if self.op.instance_name == self._old_instance_name:
911 for idx, nic in enumerate(self.nics):
912 if nic.mac == constants.VALUE_AUTO:
913 nic_mac_ini = "nic%d_mac" % idx
914 nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
916 # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
918 # ip ping checks (we use the same ip that was resolved in ExpandNames)
920 if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
921 raise errors.OpPrereqError("IP %s of instance %s already in use" %
922 (self.check_ip, self.op.instance_name),
923 errors.ECODE_NOTUNIQUE)
925 #### mac address generation
926 # By generating here the mac address both the allocator and the hooks get
927 # the real final mac address rather than the 'auto' or 'generate' value.
928 # There is a race condition between the generation and the instance object
929 # creation, which means that we know the mac is valid now, but we're not
930 # sure it will be when we actually add the instance. If things go bad
931 # adding the instance will abort because of a duplicate mac, and the
932 # creation job will fail.
933 for nic in self.nics:
934 if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
935 nic.mac = self.cfg.GenerateMAC(nic.network, self.proc.GetECId())
939 if self.op.iallocator is not None:
942 # Release all unneeded node locks
943 keep_locks = filter(None, [self.op.pnode, self.op.snode, self.op.src_node])
944 ReleaseLocks(self, locking.LEVEL_NODE, keep=keep_locks)
945 ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=keep_locks)
946 ReleaseLocks(self, locking.LEVEL_NODE_ALLOC)
948 assert (self.owned_locks(locking.LEVEL_NODE) ==
949 self.owned_locks(locking.LEVEL_NODE_RES)), \
950 "Node locks differ from node resource locks"
952 #### node related checks
955 self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
956 assert self.pnode is not None, \
957 "Cannot retrieve locked node %s" % self.op.pnode
959 raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
960 pnode.name, errors.ECODE_STATE)
962 raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
963 pnode.name, errors.ECODE_STATE)
964 if not pnode.vm_capable:
965 raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
966 " '%s'" % pnode.name, errors.ECODE_STATE)
968 self.secondaries = []
970 # Fill in any IPs from IP pools. This must happen here, because we need to
971 # know the nic's primary node, as specified by the iallocator
972 for idx, nic in enumerate(self.nics):
973 net_uuid = nic.network
974 if net_uuid is not None:
975 nobj = self.cfg.GetNetwork(net_uuid)
976 netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.name)
977 if netparams is None:
978 raise errors.OpPrereqError("No netparams found for network"
979 " %s. Propably not connected to"
980 " node's %s nodegroup" %
981 (nobj.name, self.pnode.name),
983 self.LogInfo("NIC/%d inherits netparams %s" %
984 (idx, netparams.values()))
985 nic.nicparams = dict(netparams)
986 if nic.ip is not None:
987 if nic.ip.lower() == constants.NIC_IP_POOL:
989 nic.ip = self.cfg.GenerateIp(net_uuid, self.proc.GetECId())
990 except errors.ReservationError:
991 raise errors.OpPrereqError("Unable to get a free IP for NIC %d"
992 " from the address pool" % idx,
994 self.LogInfo("Chose IP %s from network %s", nic.ip, nobj.name)
997 self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId())
998 except errors.ReservationError:
999 raise errors.OpPrereqError("IP address %s already in use"
1000 " or does not belong to network %s" %
1001 (nic.ip, nobj.name),
1002 errors.ECODE_NOTUNIQUE)
1004 # net is None, ip None or given
1005 elif self.op.conflicts_check:
1006 _CheckForConflictingIp(self, nic.ip, self.pnode.name)
1008 # mirror node verification
1009 if self.op.disk_template in constants.DTS_INT_MIRROR:
1010 if self.op.snode == pnode.name:
1011 raise errors.OpPrereqError("The secondary node cannot be the"
1012 " primary node", errors.ECODE_INVAL)
1013 CheckNodeOnline(self, self.op.snode)
1014 CheckNodeNotDrained(self, self.op.snode)
1015 CheckNodeVmCapable(self, self.op.snode)
1016 self.secondaries.append(self.op.snode)
1018 snode = self.cfg.GetNodeInfo(self.op.snode)
1019 if pnode.group != snode.group:
1020 self.LogWarning("The primary and secondary nodes are in two"
1021 " different node groups; the disk parameters"
1022 " from the first disk's node group will be"
1025 if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
1027 if self.op.disk_template in constants.DTS_INT_MIRROR:
1029 has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
1030 if compat.any(map(has_es, nodes)):
1031 raise errors.OpPrereqError("Disk template %s not supported with"
1032 " exclusive storage" % self.op.disk_template,
1035 nodenames = [pnode.name] + self.secondaries
1037 if not self.adopt_disks:
1038 if self.op.disk_template == constants.DT_RBD:
1039 # _CheckRADOSFreeSpace() is just a placeholder.
1040 # Any function that checks prerequisites can be placed here.
1041 # Check if there is enough space on the RADOS cluster.
1042 CheckRADOSFreeSpace()
1043 elif self.op.disk_template == constants.DT_EXT:
1044 # FIXME: Function that checks prereqs if needed
1047 # Check lv size requirements, if not adopting
1048 req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
1049 CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
1051 elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
1052 all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
1053 disk[constants.IDISK_ADOPT])
1054 for disk in self.disks])
1055 if len(all_lvs) != len(self.disks):
1056 raise errors.OpPrereqError("Duplicate volume names given for adoption",
1058 for lv_name in all_lvs:
1060 # FIXME: lv_name here is "vg/lv" need to ensure that other calls
1061 # to ReserveLV uses the same syntax
1062 self.cfg.ReserveLV(lv_name, self.proc.GetECId())
1063 except errors.ReservationError:
1064 raise errors.OpPrereqError("LV named %s used by another instance" %
1065 lv_name, errors.ECODE_NOTUNIQUE)
1067 vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
1068 vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
1070 node_lvs = self.rpc.call_lv_list([pnode.name],
1071 vg_names.payload.keys())[pnode.name]
1072 node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
1073 node_lvs = node_lvs.payload
1075 delta = all_lvs.difference(node_lvs.keys())
1077 raise errors.OpPrereqError("Missing logical volume(s): %s" %
1078 utils.CommaJoin(delta),
1080 online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
1082 raise errors.OpPrereqError("Online logical volumes found, cannot"
1083 " adopt: %s" % utils.CommaJoin(online_lvs),
1085 # update the size of disk based on what is found
1086 for dsk in self.disks:
1087 dsk[constants.IDISK_SIZE] = \
1088 int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
1089 dsk[constants.IDISK_ADOPT])][0]))
1091 elif self.op.disk_template == constants.DT_BLOCK:
1092 # Normalize and de-duplicate device paths
1093 all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
1094 for disk in self.disks])
1095 if len(all_disks) != len(self.disks):
1096 raise errors.OpPrereqError("Duplicate disk names given for adoption",
1098 baddisks = [d for d in all_disks
1099 if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
1101 raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
1102 " cannot be adopted" %
1103 (utils.CommaJoin(baddisks),
1104 constants.ADOPTABLE_BLOCKDEV_ROOT),
1107 node_disks = self.rpc.call_bdev_sizes([pnode.name],
1108 list(all_disks))[pnode.name]
1109 node_disks.Raise("Cannot get block device information from node %s" %
1111 node_disks = node_disks.payload
1112 delta = all_disks.difference(node_disks.keys())
1114 raise errors.OpPrereqError("Missing block device(s): %s" %
1115 utils.CommaJoin(delta),
1117 for dsk in self.disks:
1118 dsk[constants.IDISK_SIZE] = \
1119 int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
1121 # Verify instance specs
1122 spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
1124 constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
1125 constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
1126 constants.ISPEC_DISK_COUNT: len(self.disks),
1127 constants.ISPEC_DISK_SIZE: [disk[constants.IDISK_SIZE]
1128 for disk in self.disks],
1129 constants.ISPEC_NIC_COUNT: len(self.nics),
1130 constants.ISPEC_SPINDLE_USE: spindle_use,
1133 group_info = self.cfg.GetNodeGroup(pnode.group)
1134 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1135 res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec,
1136 self.op.disk_template)
1137 if not self.op.ignore_ipolicy and res:
1138 msg = ("Instance allocation to group %s (%s) violates policy: %s" %
1139 (pnode.group, group_info.name, utils.CommaJoin(res)))
1140 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1142 CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
1144 CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
1145 # check OS parameters (remotely)
1146 CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
1148 CheckNicsBridgesExist(self, self.nics, self.pnode.name)
1150 #TODO: _CheckExtParams (remotely)
1151 # Check parameters for extstorage
1153 # memory check on primary node
1154 #TODO(dynmem): use MINMEM for checking
1156 CheckNodeFreeMemory(self, self.pnode.name,
1157 "creating instance %s" % self.op.instance_name,
1158 self.be_full[constants.BE_MAXMEM],
1161 self.dry_run_result = list(nodenames)
1163 def Exec(self, feedback_fn):
1164 """Create and add the instance to the cluster.
1167 instance = self.op.instance_name
1168 pnode_name = self.pnode.name
1170 assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
1171 self.owned_locks(locking.LEVEL_NODE)), \
1172 "Node locks differ from node resource locks"
1173 assert not self.glm.is_owned(locking.LEVEL_NODE_ALLOC)
1175 ht_kind = self.op.hypervisor
1176 if ht_kind in constants.HTS_REQ_PORT:
1177 network_port = self.cfg.AllocatePort()
1181 # This is ugly but we got a chicken-egg problem here
1182 # We can only take the group disk parameters, as the instance
1183 # has no disks yet (we are generating them right here).
1184 node = self.cfg.GetNodeInfo(pnode_name)
1185 nodegroup = self.cfg.GetNodeGroup(node.group)
1186 disks = GenerateDiskTemplate(self,
1187 self.op.disk_template,
1188 instance, pnode_name,
1191 self.instance_file_storage_dir,
1192 self.op.file_driver,
1195 self.cfg.GetGroupDiskParams(nodegroup))
1197 iobj = objects.Instance(name=instance, os=self.op.os_type,
1198 primary_node=pnode_name,
1199 nics=self.nics, disks=disks,
1200 disk_template=self.op.disk_template,
1201 admin_state=constants.ADMINST_DOWN,
1202 network_port=network_port,
1203 beparams=self.op.beparams,
1204 hvparams=self.op.hvparams,
1205 hypervisor=self.op.hypervisor,
1206 osparams=self.op.osparams,
1210 for tag in self.op.tags:
1213 if self.adopt_disks:
1214 if self.op.disk_template == constants.DT_PLAIN:
1215 # rename LVs to the newly-generated names; we need to construct
1216 # 'fake' LV disks with the old data, plus the new unique_id
1217 tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
1219 for t_dsk, a_dsk in zip(tmp_disks, self.disks):
1220 rename_to.append(t_dsk.logical_id)
1221 t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
1222 self.cfg.SetDiskID(t_dsk, pnode_name)
1223 result = self.rpc.call_blockdev_rename(pnode_name,
1224 zip(tmp_disks, rename_to))
1225 result.Raise("Failed to rename adoped LVs")
1227 feedback_fn("* creating instance disks...")
1229 CreateDisks(self, iobj)
1230 except errors.OpExecError:
1231 self.LogWarning("Device creation failed")
1232 self.cfg.ReleaseDRBDMinors(instance)
1235 feedback_fn("adding instance %s to cluster config" % instance)
1237 self.cfg.AddInstance(iobj, self.proc.GetECId())
1239 # Declare that we don't want to remove the instance lock anymore, as we've
1240 # added the instance to the config
1241 del self.remove_locks[locking.LEVEL_INSTANCE]
1243 if self.op.mode == constants.INSTANCE_IMPORT:
1244 # Release unused nodes
1245 ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
1248 ReleaseLocks(self, locking.LEVEL_NODE)
1251 if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
1252 feedback_fn("* wiping instance disks...")
1254 WipeDisks(self, iobj)
1255 except errors.OpExecError, err:
1256 logging.exception("Wiping disks failed")
1257 self.LogWarning("Wiping instance disks failed (%s)", err)
1261 # Something is already wrong with the disks, don't do anything else
1263 elif self.op.wait_for_sync:
1264 disk_abort = not WaitForSync(self, iobj)
1265 elif iobj.disk_template in constants.DTS_INT_MIRROR:
1266 # make sure the disks are not degraded (still sync-ing is ok)
1267 feedback_fn("* checking mirrors status")
1268 disk_abort = not WaitForSync(self, iobj, oneshot=True)
1273 RemoveDisks(self, iobj)
1274 self.cfg.RemoveInstance(iobj.name)
1275 # Make sure the instance lock gets removed
1276 self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
1277 raise errors.OpExecError("There are some degraded disks for"
1280 # Release all node resource locks
1281 ReleaseLocks(self, locking.LEVEL_NODE_RES)
1283 if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
1284 # we need to set the disks ID to the primary node, since the
1285 # preceding code might or might have not done it, depending on
1286 # disk template and other options
1287 for disk in iobj.disks:
1288 self.cfg.SetDiskID(disk, pnode_name)
1289 if self.op.mode == constants.INSTANCE_CREATE:
1290 if not self.op.no_install:
1291 pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
1292 not self.op.wait_for_sync)
1294 feedback_fn("* pausing disk sync to install instance OS")
1295 result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1298 for idx, success in enumerate(result.payload):
1300 logging.warn("pause-sync of instance %s for disk %d failed",
1303 feedback_fn("* running the instance OS create scripts...")
1304 # FIXME: pass debug option from opcode to backend
1306 self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
1307 self.op.debug_level)
1309 feedback_fn("* resuming disk sync")
1310 result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1313 for idx, success in enumerate(result.payload):
1315 logging.warn("resume-sync of instance %s for disk %d failed",
1318 os_add_result.Raise("Could not add os for instance %s"
1319 " on node %s" % (instance, pnode_name))
1322 if self.op.mode == constants.INSTANCE_IMPORT:
1323 feedback_fn("* running the instance OS import scripts...")
1327 for idx, image in enumerate(self.src_images):
1331 # FIXME: pass debug option from opcode to backend
1332 dt = masterd.instance.DiskTransfer("disk/%s" % idx,
1333 constants.IEIO_FILE, (image, ),
1334 constants.IEIO_SCRIPT,
1335 (iobj.disks[idx], idx),
1337 transfers.append(dt)
1340 masterd.instance.TransferInstanceData(self, feedback_fn,
1341 self.op.src_node, pnode_name,
1342 self.pnode.secondary_ip,
1344 if not compat.all(import_result):
1345 self.LogWarning("Some disks for instance %s on node %s were not"
1346 " imported successfully" % (instance, pnode_name))
1348 rename_from = self._old_instance_name
1350 elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
1351 feedback_fn("* preparing remote import...")
1352 # The source cluster will stop the instance before attempting to make
1353 # a connection. In some cases stopping an instance can take a long
1354 # time, hence the shutdown timeout is added to the connection
1356 connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
1357 self.op.source_shutdown_timeout)
1358 timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
1360 assert iobj.primary_node == self.pnode.name
1362 masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
1363 self.source_x509_ca,
1364 self._cds, timeouts)
1365 if not compat.all(disk_results):
1366 # TODO: Should the instance still be started, even if some disks
1367 # failed to import (valid for local imports, too)?
1368 self.LogWarning("Some disks for instance %s on node %s were not"
1369 " imported successfully" % (instance, pnode_name))
1371 rename_from = self.source_instance_name
1374 # also checked in the prereq part
1375 raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
1378 # Run rename script on newly imported instance
1379 assert iobj.name == instance
1380 feedback_fn("Running rename script for %s" % instance)
1381 result = self.rpc.call_instance_run_rename(pnode_name, iobj,
1383 self.op.debug_level)
1385 self.LogWarning("Failed to run rename script for %s on node"
1386 " %s: %s" % (instance, pnode_name, result.fail_msg))
1388 assert not self.owned_locks(locking.LEVEL_NODE_RES)
1391 iobj.admin_state = constants.ADMINST_UP
1392 self.cfg.Update(iobj, feedback_fn)
1393 logging.info("Starting instance %s on node %s", instance, pnode_name)
1394 feedback_fn("* starting instance...")
1395 result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
1396 False, self.op.reason)
1397 result.Raise("Could not start instance")
1399 return list(iobj.all_nodes)
1402 class LUInstanceRename(LogicalUnit):
1403 """Rename an instance.
1406 HPATH = "instance-rename"
1407 HTYPE = constants.HTYPE_INSTANCE
1409 def CheckArguments(self):
1413 if self.op.ip_check and not self.op.name_check:
1414 # TODO: make the ip check more flexible and not depend on the name check
1415 raise errors.OpPrereqError("IP address check requires a name check",
1418 def BuildHooksEnv(self):
1421 This runs on master, primary and secondary nodes of the instance.
1424 env = BuildInstanceHookEnvByObject(self, self.instance)
1425 env["INSTANCE_NEW_NAME"] = self.op.new_name
1428 def BuildHooksNodes(self):
1429 """Build hooks nodes.
1432 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
1435 def CheckPrereq(self):
1436 """Check prerequisites.
1438 This checks that the instance is in the cluster and is not running.
1441 self.op.instance_name = ExpandInstanceName(self.cfg,
1442 self.op.instance_name)
1443 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1444 assert instance is not None
1445 CheckNodeOnline(self, instance.primary_node)
1446 CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
1447 msg="cannot rename")
1448 self.instance = instance
1450 new_name = self.op.new_name
1451 if self.op.name_check:
1452 hostname = _CheckHostnameSane(self, new_name)
1453 new_name = self.op.new_name = hostname.name
1454 if (self.op.ip_check and
1455 netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
1456 raise errors.OpPrereqError("IP %s of instance %s already in use" %
1457 (hostname.ip, new_name),
1458 errors.ECODE_NOTUNIQUE)
1460 instance_list = self.cfg.GetInstanceList()
1461 if new_name in instance_list and new_name != instance.name:
1462 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
1463 new_name, errors.ECODE_EXISTS)
1465 def Exec(self, feedback_fn):
1466 """Rename the instance.
1469 inst = self.instance
1470 old_name = inst.name
1472 rename_file_storage = False
1473 if (inst.disk_template in constants.DTS_FILEBASED and
1474 self.op.new_name != inst.name):
1475 old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1476 rename_file_storage = True
1478 self.cfg.RenameInstance(inst.name, self.op.new_name)
1479 # Change the instance lock. This is definitely safe while we hold the BGL.
1480 # Otherwise the new lock would have to be added in acquired mode.
1482 assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER)
1483 self.glm.remove(locking.LEVEL_INSTANCE, old_name)
1484 self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
1486 # re-read the instance from the configuration after rename
1487 inst = self.cfg.GetInstanceInfo(self.op.new_name)
1489 if rename_file_storage:
1490 new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1491 result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
1492 old_file_storage_dir,
1493 new_file_storage_dir)
1494 result.Raise("Could not rename on node %s directory '%s' to '%s'"
1495 " (but the instance has been renamed in Ganeti)" %
1496 (inst.primary_node, old_file_storage_dir,
1497 new_file_storage_dir))
1499 StartInstanceDisks(self, inst, None)
1500 # update info on disks
1501 info = GetInstanceInfoText(inst)
1502 for (idx, disk) in enumerate(inst.disks):
1503 for node in inst.all_nodes:
1504 self.cfg.SetDiskID(disk, node)
1505 result = self.rpc.call_blockdev_setinfo(node, disk, info)
1507 self.LogWarning("Error setting info on node %s for disk %s: %s",
1508 node, idx, result.fail_msg)
1510 result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
1511 old_name, self.op.debug_level)
1512 msg = result.fail_msg
1514 msg = ("Could not run OS rename script for instance %s on node %s"
1515 " (but the instance has been renamed in Ganeti): %s" %
1516 (inst.name, inst.primary_node, msg))
1517 self.LogWarning(msg)
1519 ShutdownInstanceDisks(self, inst)
1524 class LUInstanceRemove(LogicalUnit):
1525 """Remove an instance.
1528 HPATH = "instance-remove"
1529 HTYPE = constants.HTYPE_INSTANCE
1532 def ExpandNames(self):
1533 self._ExpandAndLockInstance()
1534 self.needed_locks[locking.LEVEL_NODE] = []
1535 self.needed_locks[locking.LEVEL_NODE_RES] = []
1536 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
1538 def DeclareLocks(self, level):
1539 if level == locking.LEVEL_NODE:
1540 self._LockInstancesNodes()
1541 elif level == locking.LEVEL_NODE_RES:
1543 self.needed_locks[locking.LEVEL_NODE_RES] = \
1544 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1546 def BuildHooksEnv(self):
1549 This runs on master, primary and secondary nodes of the instance.
1552 env = BuildInstanceHookEnvByObject(self, self.instance)
1553 env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
1556 def BuildHooksNodes(self):
1557 """Build hooks nodes.
1560 nl = [self.cfg.GetMasterNode()]
1561 nl_post = list(self.instance.all_nodes) + nl
1562 return (nl, nl_post)
1564 def CheckPrereq(self):
1565 """Check prerequisites.
1567 This checks that the instance is in the cluster.
1570 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1571 assert self.instance is not None, \
1572 "Cannot retrieve locked instance %s" % self.op.instance_name
1574 def Exec(self, feedback_fn):
1575 """Remove the instance.
1578 instance = self.instance
1579 logging.info("Shutting down instance %s on node %s",
1580 instance.name, instance.primary_node)
1582 result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
1583 self.op.shutdown_timeout,
1585 msg = result.fail_msg
1587 if self.op.ignore_failures:
1588 feedback_fn("Warning: can't shutdown instance: %s" % msg)
1590 raise errors.OpExecError("Could not shutdown instance %s on"
1592 (instance.name, instance.primary_node, msg))
1594 assert (self.owned_locks(locking.LEVEL_NODE) ==
1595 self.owned_locks(locking.LEVEL_NODE_RES))
1596 assert not (set(instance.all_nodes) -
1597 self.owned_locks(locking.LEVEL_NODE)), \
1598 "Not owning correct locks"
1600 RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
1603 class LUInstanceMove(LogicalUnit):
1604 """Move an instance by data-copying.
1607 HPATH = "instance-move"
1608 HTYPE = constants.HTYPE_INSTANCE
1611 def ExpandNames(self):
1612 self._ExpandAndLockInstance()
1613 target_node = ExpandNodeName(self.cfg, self.op.target_node)
1614 self.op.target_node = target_node
1615 self.needed_locks[locking.LEVEL_NODE] = [target_node]
1616 self.needed_locks[locking.LEVEL_NODE_RES] = []
1617 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
1619 def DeclareLocks(self, level):
1620 if level == locking.LEVEL_NODE:
1621 self._LockInstancesNodes(primary_only=True)
1622 elif level == locking.LEVEL_NODE_RES:
1624 self.needed_locks[locking.LEVEL_NODE_RES] = \
1625 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1627 def BuildHooksEnv(self):
1630 This runs on master, primary and secondary nodes of the instance.
1634 "TARGET_NODE": self.op.target_node,
1635 "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
1637 env.update(BuildInstanceHookEnvByObject(self, self.instance))
1640 def BuildHooksNodes(self):
1641 """Build hooks nodes.
1645 self.cfg.GetMasterNode(),
1646 self.instance.primary_node,
1647 self.op.target_node,
1651 def CheckPrereq(self):
1652 """Check prerequisites.
1654 This checks that the instance is in the cluster.
1657 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1658 assert self.instance is not None, \
1659 "Cannot retrieve locked instance %s" % self.op.instance_name
1661 if instance.disk_template not in constants.DTS_COPYABLE:
1662 raise errors.OpPrereqError("Disk template %s not suitable for copying" %
1663 instance.disk_template, errors.ECODE_STATE)
1665 node = self.cfg.GetNodeInfo(self.op.target_node)
1666 assert node is not None, \
1667 "Cannot retrieve locked node %s" % self.op.target_node
1669 self.target_node = target_node = node.name
1671 if target_node == instance.primary_node:
1672 raise errors.OpPrereqError("Instance %s is already on the node %s" %
1673 (instance.name, target_node),
1676 bep = self.cfg.GetClusterInfo().FillBE(instance)
1678 for idx, dsk in enumerate(instance.disks):
1679 if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
1680 raise errors.OpPrereqError("Instance disk %d has a complex layout,"
1681 " cannot copy" % idx, errors.ECODE_STATE)
1683 CheckNodeOnline(self, target_node)
1684 CheckNodeNotDrained(self, target_node)
1685 CheckNodeVmCapable(self, target_node)
1686 cluster = self.cfg.GetClusterInfo()
1687 group_info = self.cfg.GetNodeGroup(node.group)
1688 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1689 CheckTargetNodeIPolicy(self, ipolicy, instance, node, self.cfg,
1690 ignore=self.op.ignore_ipolicy)
1692 if instance.admin_state == constants.ADMINST_UP:
1693 # check memory requirements on the secondary node
1694 CheckNodeFreeMemory(self, target_node,
1695 "failing over instance %s" %
1696 instance.name, bep[constants.BE_MAXMEM],
1697 instance.hypervisor)
1699 self.LogInfo("Not checking memory on the secondary node as"
1700 " instance will not be started")
1702 # check bridge existance
1703 CheckInstanceBridgesExist(self, instance, node=target_node)
1705 def Exec(self, feedback_fn):
1706 """Move an instance.
1708 The move is done by shutting it down on its present node, copying
1709 the data over (slow) and starting it on the new node.
1712 instance = self.instance
1714 source_node = instance.primary_node
1715 target_node = self.target_node
1717 self.LogInfo("Shutting down instance %s on source node %s",
1718 instance.name, source_node)
1720 assert (self.owned_locks(locking.LEVEL_NODE) ==
1721 self.owned_locks(locking.LEVEL_NODE_RES))
1723 result = self.rpc.call_instance_shutdown(source_node, instance,
1724 self.op.shutdown_timeout,
1726 msg = result.fail_msg
1728 if self.op.ignore_consistency:
1729 self.LogWarning("Could not shutdown instance %s on node %s."
1730 " Proceeding anyway. Please make sure node"
1731 " %s is down. Error details: %s",
1732 instance.name, source_node, source_node, msg)
1734 raise errors.OpExecError("Could not shutdown instance %s on"
1736 (instance.name, source_node, msg))
1738 # create the target disks
1740 CreateDisks(self, instance, target_node=target_node)
1741 except errors.OpExecError:
1742 self.LogWarning("Device creation failed")
1743 self.cfg.ReleaseDRBDMinors(instance.name)
1746 cluster_name = self.cfg.GetClusterInfo().cluster_name
1749 # activate, get path, copy the data over
1750 for idx, disk in enumerate(instance.disks):
1751 self.LogInfo("Copying data for disk %d", idx)
1752 result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
1753 instance.name, True, idx)
1755 self.LogWarning("Can't assemble newly created disk %d: %s",
1756 idx, result.fail_msg)
1757 errs.append(result.fail_msg)
1759 dev_path = result.payload
1760 result = self.rpc.call_blockdev_export(source_node, (disk, instance),
1761 target_node, dev_path,
1764 self.LogWarning("Can't copy data over for disk %d: %s",
1765 idx, result.fail_msg)
1766 errs.append(result.fail_msg)
1770 self.LogWarning("Some disks failed to copy, aborting")
1772 RemoveDisks(self, instance, target_node=target_node)
1774 self.cfg.ReleaseDRBDMinors(instance.name)
1775 raise errors.OpExecError("Errors during disk copy: %s" %
1778 instance.primary_node = target_node
1779 self.cfg.Update(instance, feedback_fn)
1781 self.LogInfo("Removing the disks on the original node")
1782 RemoveDisks(self, instance, target_node=source_node)
1784 # Only start the instance if it's marked as up
1785 if instance.admin_state == constants.ADMINST_UP:
1786 self.LogInfo("Starting instance %s on node %s",
1787 instance.name, target_node)
1789 disks_ok, _ = AssembleInstanceDisks(self, instance,
1790 ignore_secondaries=True)
1792 ShutdownInstanceDisks(self, instance)
1793 raise errors.OpExecError("Can't activate the instance's disks")
1795 result = self.rpc.call_instance_start(target_node,
1796 (instance, None, None), False,
1798 msg = result.fail_msg
1800 ShutdownInstanceDisks(self, instance)
1801 raise errors.OpExecError("Could not start instance %s on node %s: %s" %
1802 (instance.name, target_node, msg))
1805 class LUInstanceMultiAlloc(NoHooksLU):
1806 """Allocates multiple instances at the same time.
1811 def CheckArguments(self):
1816 for inst in self.op.instances:
1817 if inst.iallocator is not None:
1818 raise errors.OpPrereqError("iallocator are not allowed to be set on"
1819 " instance objects", errors.ECODE_INVAL)
1820 nodes.append(bool(inst.pnode))
1821 if inst.disk_template in constants.DTS_INT_MIRROR:
1822 nodes.append(bool(inst.snode))
1824 has_nodes = compat.any(nodes)
1825 if compat.all(nodes) ^ has_nodes:
1826 raise errors.OpPrereqError("There are instance objects providing"
1827 " pnode/snode while others do not",
1830 if self.op.iallocator is None:
1831 default_iallocator = self.cfg.GetDefaultIAllocator()
1832 if default_iallocator and has_nodes:
1833 self.op.iallocator = default_iallocator
1835 raise errors.OpPrereqError("No iallocator or nodes on the instances"
1836 " given and no cluster-wide default"
1837 " iallocator found; please specify either"
1838 " an iallocator or nodes on the instances"
1839 " or set a cluster-wide default iallocator",
1842 _CheckOpportunisticLocking(self.op)
1844 dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
1846 raise errors.OpPrereqError("There are duplicate instance names: %s" %
1847 utils.CommaJoin(dups), errors.ECODE_INVAL)
1849 def ExpandNames(self):
1850 """Calculate the locks.
1853 self.share_locks = ShareAll()
1854 self.needed_locks = {
1855 # iallocator will select nodes and even if no iallocator is used,
1856 # collisions with LUInstanceCreate should be avoided
1857 locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
1860 if self.op.iallocator:
1861 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
1862 self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
1864 if self.op.opportunistic_locking:
1865 self.opportunistic_locks[locking.LEVEL_NODE] = True
1866 self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
1869 for inst in self.op.instances:
1870 inst.pnode = ExpandNodeName(self.cfg, inst.pnode)
1871 nodeslist.append(inst.pnode)
1872 if inst.snode is not None:
1873 inst.snode = ExpandNodeName(self.cfg, inst.snode)
1874 nodeslist.append(inst.snode)
1876 self.needed_locks[locking.LEVEL_NODE] = nodeslist
1877 # Lock resources of instance's primary and secondary nodes (copy to
1878 # prevent accidential modification)
1879 self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
1881 def CheckPrereq(self):
1882 """Check prerequisite.
1885 cluster = self.cfg.GetClusterInfo()
1886 default_vg = self.cfg.GetVGName()
1887 ec_id = self.proc.GetECId()
1889 if self.op.opportunistic_locking:
1890 # Only consider nodes for which a lock is held
1891 node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
1893 node_whitelist = None
1895 insts = [_CreateInstanceAllocRequest(op, ComputeDisks(op, default_vg),
1896 _ComputeNics(op, cluster, None,
1898 _ComputeFullBeParams(op, cluster),
1900 for op in self.op.instances]
1902 req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
1903 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
1905 ial.Run(self.op.iallocator)
1908 raise errors.OpPrereqError("Can't compute nodes using"
1909 " iallocator '%s': %s" %
1910 (self.op.iallocator, ial.info),
1913 self.ia_result = ial.result
1916 self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
1917 constants.JOB_IDS_KEY: [],
1920 def _ConstructPartialResult(self):
1921 """Contructs the partial result.
1924 (allocatable, failed) = self.ia_result
1926 opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY:
1927 map(compat.fst, allocatable),
1928 opcodes.OpInstanceMultiAlloc.FAILED_KEY: failed,
1931 def Exec(self, feedback_fn):
1932 """Executes the opcode.
1935 op2inst = dict((op.instance_name, op) for op in self.op.instances)
1936 (allocatable, failed) = self.ia_result
1939 for (name, nodes) in allocatable:
1940 op = op2inst.pop(name)
1943 (op.pnode, op.snode) = nodes
1949 missing = set(op2inst.keys()) - set(failed)
1950 assert not missing, \
1951 "Iallocator did return incomplete result: %s" % utils.CommaJoin(missing)
1953 return ResultWithJobs(jobs, **self._ConstructPartialResult())
1956 class _InstNicModPrivate:
1957 """Data structure for network interface modifications.
1959 Used by L{LUInstanceSetParams}.
1967 def _PrepareContainerMods(mods, private_fn):
1968 """Prepares a list of container modifications by adding a private data field.
1970 @type mods: list of tuples; (operation, index, parameters)
1971 @param mods: List of modifications
1972 @type private_fn: callable or None
1973 @param private_fn: Callable for constructing a private data field for a
1978 if private_fn is None:
1983 return [(op, idx, params, fn()) for (op, idx, params) in mods]
1986 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
1987 """Checks if nodes have enough physical CPUs
1989 This function checks if all given nodes have the needed number of
1990 physical CPUs. In case any node has less CPUs or we cannot get the
1991 information from the node, this function raises an OpPrereqError
1994 @type lu: C{LogicalUnit}
1995 @param lu: a logical unit from which we get configuration data
1996 @type nodenames: C{list}
1997 @param nodenames: the list of node names to check
1998 @type requested: C{int}
1999 @param requested: the minimum acceptable number of physical CPUs
2000 @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
2001 or we cannot check the node
2004 nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name], None)
2005 for node in nodenames:
2006 info = nodeinfo[node]
2007 info.Raise("Cannot get current information from node %s" % node,
2008 prereq=True, ecode=errors.ECODE_ENVIRON)
2009 (_, _, (hv_info, )) = info.payload
2010 num_cpus = hv_info.get("cpu_total", None)
2011 if not isinstance(num_cpus, int):
2012 raise errors.OpPrereqError("Can't compute the number of physical CPUs"
2013 " on node %s, result was '%s'" %
2014 (node, num_cpus), errors.ECODE_ENVIRON)
2015 if requested > num_cpus:
2016 raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
2017 "required" % (node, num_cpus, requested),
2021 def GetItemFromContainer(identifier, kind, container):
2022 """Return the item refered by the identifier.
2024 @type identifier: string
2025 @param identifier: Item index or name or UUID
2027 @param kind: One-word item description
2028 @type container: list
2029 @param container: Container to get the item from
2034 idx = int(identifier)
2037 absidx = len(container) - 1
2039 raise IndexError("Not accepting negative indices other than -1")
2040 elif idx > len(container):
2041 raise IndexError("Got %s index %s, but there are only %s" %
2042 (kind, idx, len(container)))
2045 return (absidx, container[idx])
2049 for idx, item in enumerate(container):
2050 if item.uuid == identifier or item.name == identifier:
2053 raise errors.OpPrereqError("Cannot find %s with identifier %s" %
2054 (kind, identifier), errors.ECODE_NOENT)
2057 def _ApplyContainerMods(kind, container, chgdesc, mods,
2058 create_fn, modify_fn, remove_fn):
2059 """Applies descriptions in C{mods} to C{container}.
2062 @param kind: One-word item description
2063 @type container: list
2064 @param container: Container to modify
2065 @type chgdesc: None or list
2066 @param chgdesc: List of applied changes
2068 @param mods: Modifications as returned by L{_PrepareContainerMods}
2069 @type create_fn: callable
2070 @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
2071 receives absolute item index, parameters and private data object as added
2072 by L{_PrepareContainerMods}, returns tuple containing new item and changes
2074 @type modify_fn: callable
2075 @param modify_fn: Callback for modifying an existing item
2076 (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
2077 and private data object as added by L{_PrepareContainerMods}, returns
2079 @type remove_fn: callable
2080 @param remove_fn: Callback on removing item; receives absolute item index,
2081 item and private data object as added by L{_PrepareContainerMods}
2084 for (op, identifier, params, private) in mods:
2087 if op == constants.DDM_ADD:
2088 # Calculate where item will be added
2089 # When adding an item, identifier can only be an index
2091 idx = int(identifier)
2093 raise errors.OpPrereqError("Only possitive integer or -1 is accepted as"
2094 " identifier for %s" % constants.DDM_ADD,
2097 addidx = len(container)
2100 raise IndexError("Not accepting negative indices other than -1")
2101 elif idx > len(container):
2102 raise IndexError("Got %s index %s, but there are only %s" %
2103 (kind, idx, len(container)))
2106 if create_fn is None:
2109 (item, changes) = create_fn(addidx, params, private)
2112 container.append(item)
2115 assert idx <= len(container)
2116 # list.insert does so before the specified index
2117 container.insert(idx, item)
2119 # Retrieve existing item
2120 (absidx, item) = GetItemFromContainer(identifier, kind, container)
2122 if op == constants.DDM_REMOVE:
2125 if remove_fn is not None:
2126 remove_fn(absidx, item, private)
2128 changes = [("%s/%s" % (kind, absidx), "remove")]
2130 assert container[absidx] == item
2131 del container[absidx]
2132 elif op == constants.DDM_MODIFY:
2133 if modify_fn is not None:
2134 changes = modify_fn(absidx, item, params, private)
2136 raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2138 assert _TApplyContModsCbChanges(changes)
2140 if not (chgdesc is None or changes is None):
2141 chgdesc.extend(changes)
2144 def _UpdateIvNames(base_index, disks):
2145 """Updates the C{iv_name} attribute of disks.
2147 @type disks: list of L{objects.Disk}
2150 for (idx, disk) in enumerate(disks):
2151 disk.iv_name = "disk/%s" % (base_index + idx, )
2154 class LUInstanceSetParams(LogicalUnit):
2155 """Modifies an instances's parameters.
2158 HPATH = "instance-modify"
2159 HTYPE = constants.HTYPE_INSTANCE
2163 def _UpgradeDiskNicMods(kind, mods, verify_fn):
2164 assert ht.TList(mods)
2165 assert not mods or len(mods[0]) in (2, 3)
2167 if mods and len(mods[0]) == 2:
2171 for op, params in mods:
2172 if op in (constants.DDM_ADD, constants.DDM_REMOVE):
2173 result.append((op, -1, params))
2177 raise errors.OpPrereqError("Only one %s add or remove operation is"
2178 " supported at a time" % kind,
2181 result.append((constants.DDM_MODIFY, op, params))
2183 assert verify_fn(result)
2190 def _CheckMods(kind, mods, key_types, item_fn):
2191 """Ensures requested disk/NIC modifications are valid.
2194 for (op, _, params) in mods:
2195 assert ht.TDict(params)
2197 # If 'key_types' is an empty dict, we assume we have an
2198 # 'ext' template and thus do not ForceDictType
2200 utils.ForceDictType(params, key_types)
2202 if op == constants.DDM_REMOVE:
2204 raise errors.OpPrereqError("No settings should be passed when"
2205 " removing a %s" % kind,
2207 elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
2210 raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2213 def _VerifyDiskModification(op, params):
2214 """Verifies a disk modification.
2217 if op == constants.DDM_ADD:
2218 mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
2219 if mode not in constants.DISK_ACCESS_SET:
2220 raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
2223 size = params.get(constants.IDISK_SIZE, None)
2225 raise errors.OpPrereqError("Required disk parameter '%s' missing" %
2226 constants.IDISK_SIZE, errors.ECODE_INVAL)
2230 except (TypeError, ValueError), err:
2231 raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
2234 params[constants.IDISK_SIZE] = size
2235 name = params.get(constants.IDISK_NAME, None)
2236 if name is not None and name.lower() == constants.VALUE_NONE:
2237 params[constants.IDISK_NAME] = None
2239 elif op == constants.DDM_MODIFY:
2240 if constants.IDISK_SIZE in params:
2241 raise errors.OpPrereqError("Disk size change not possible, use"
2242 " grow-disk", errors.ECODE_INVAL)
2244 raise errors.OpPrereqError("Disk modification doesn't support"
2245 " additional arbitrary parameters",
2247 name = params.get(constants.IDISK_NAME, None)
2248 if name is not None and name.lower() == constants.VALUE_NONE:
2249 params[constants.IDISK_NAME] = None
2252 def _VerifyNicModification(op, params):
2253 """Verifies a network interface modification.
2256 if op in (constants.DDM_ADD, constants.DDM_MODIFY):
2257 ip = params.get(constants.INIC_IP, None)
2258 name = params.get(constants.INIC_NAME, None)
2259 req_net = params.get(constants.INIC_NETWORK, None)
2260 link = params.get(constants.NIC_LINK, None)
2261 mode = params.get(constants.NIC_MODE, None)
2262 if name is not None and name.lower() == constants.VALUE_NONE:
2263 params[constants.INIC_NAME] = None
2264 if req_net is not None:
2265 if req_net.lower() == constants.VALUE_NONE:
2266 params[constants.INIC_NETWORK] = None
2268 elif link is not None or mode is not None:
2269 raise errors.OpPrereqError("If network is given"
2270 " mode or link should not",
2273 if op == constants.DDM_ADD:
2274 macaddr = params.get(constants.INIC_MAC, None)
2276 params[constants.INIC_MAC] = constants.VALUE_AUTO
2279 if ip.lower() == constants.VALUE_NONE:
2280 params[constants.INIC_IP] = None
2282 if ip.lower() == constants.NIC_IP_POOL:
2283 if op == constants.DDM_ADD and req_net is None:
2284 raise errors.OpPrereqError("If ip=pool, parameter network"
2288 if not netutils.IPAddress.IsValid(ip):
2289 raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
2292 if constants.INIC_MAC in params:
2293 macaddr = params[constants.INIC_MAC]
2294 if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2295 macaddr = utils.NormalizeAndValidateMac(macaddr)
2297 if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
2298 raise errors.OpPrereqError("'auto' is not a valid MAC address when"
2299 " modifying an existing NIC",
2302 def CheckArguments(self):
2303 if not (self.op.nics or self.op.disks or self.op.disk_template or
2304 self.op.hvparams or self.op.beparams or self.op.os_name or
2305 self.op.offline is not None or self.op.runtime_mem or
2307 raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
2309 if self.op.hvparams:
2310 CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
2311 "hypervisor", "instance", "cluster")
2313 self.op.disks = self._UpgradeDiskNicMods(
2314 "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
2315 self.op.nics = self._UpgradeDiskNicMods(
2316 "NIC", self.op.nics, opcodes.OpInstanceSetParams.TestNicModifications)
2318 if self.op.disks and self.op.disk_template is not None:
2319 raise errors.OpPrereqError("Disk template conversion and other disk"
2320 " changes not supported at the same time",
2323 if (self.op.disk_template and
2324 self.op.disk_template in constants.DTS_INT_MIRROR and
2325 self.op.remote_node is None):
2326 raise errors.OpPrereqError("Changing the disk template to a mirrored"
2327 " one requires specifying a secondary node",
2330 # Check NIC modifications
2331 self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
2332 self._VerifyNicModification)
2335 self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
2337 def ExpandNames(self):
2338 self._ExpandAndLockInstance()
2339 self.needed_locks[locking.LEVEL_NODEGROUP] = []
2340 # Can't even acquire node locks in shared mode as upcoming changes in
2341 # Ganeti 2.6 will start to modify the node object on disk conversion
2342 self.needed_locks[locking.LEVEL_NODE] = []
2343 self.needed_locks[locking.LEVEL_NODE_RES] = []
2344 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2345 # Look node group to look up the ipolicy
2346 self.share_locks[locking.LEVEL_NODEGROUP] = 1
2348 def DeclareLocks(self, level):
2349 if level == locking.LEVEL_NODEGROUP:
2350 assert not self.needed_locks[locking.LEVEL_NODEGROUP]
2351 # Acquire locks for the instance's nodegroups optimistically. Needs
2352 # to be verified in CheckPrereq
2353 self.needed_locks[locking.LEVEL_NODEGROUP] = \
2354 self.cfg.GetInstanceNodeGroups(self.op.instance_name)
2355 elif level == locking.LEVEL_NODE:
2356 self._LockInstancesNodes()
2357 if self.op.disk_template and self.op.remote_node:
2358 self.op.remote_node = ExpandNodeName(self.cfg, self.op.remote_node)
2359 self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
2360 elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
2362 self.needed_locks[locking.LEVEL_NODE_RES] = \
2363 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
2365 def BuildHooksEnv(self):
2368 This runs on the master, primary and secondaries.
2372 if constants.BE_MINMEM in self.be_new:
2373 args["minmem"] = self.be_new[constants.BE_MINMEM]
2374 if constants.BE_MAXMEM in self.be_new:
2375 args["maxmem"] = self.be_new[constants.BE_MAXMEM]
2376 if constants.BE_VCPUS in self.be_new:
2377 args["vcpus"] = self.be_new[constants.BE_VCPUS]
2378 # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
2379 # information at all.
2381 if self._new_nics is not None:
2384 for nic in self._new_nics:
2385 n = copy.deepcopy(nic)
2386 nicparams = self.cluster.SimpleFillNIC(n.nicparams)
2387 n.nicparams = nicparams
2388 nics.append(NICToTuple(self, n))
2392 env = BuildInstanceHookEnvByObject(self, self.instance, override=args)
2393 if self.op.disk_template:
2394 env["NEW_DISK_TEMPLATE"] = self.op.disk_template
2395 if self.op.runtime_mem:
2396 env["RUNTIME_MEMORY"] = self.op.runtime_mem
2400 def BuildHooksNodes(self):
2401 """Build hooks nodes.
2404 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
2407 def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
2408 old_params, cluster, pnode):
2410 update_params_dict = dict([(key, params[key])
2411 for key in constants.NICS_PARAMETERS
2414 req_link = update_params_dict.get(constants.NIC_LINK, None)
2415 req_mode = update_params_dict.get(constants.NIC_MODE, None)
2418 new_net_uuid_or_name = params.get(constants.INIC_NETWORK, old_net_uuid)
2419 if new_net_uuid_or_name:
2420 new_net_uuid = self.cfg.LookupNetwork(new_net_uuid_or_name)
2421 new_net_obj = self.cfg.GetNetwork(new_net_uuid)
2424 old_net_obj = self.cfg.GetNetwork(old_net_uuid)
2427 netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode)
2429 raise errors.OpPrereqError("No netparams found for the network"
2430 " %s, probably not connected" %
2431 new_net_obj.name, errors.ECODE_INVAL)
2432 new_params = dict(netparams)
2434 new_params = GetUpdatedParams(old_params, update_params_dict)
2436 utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
2438 new_filled_params = cluster.SimpleFillNIC(new_params)
2439 objects.NIC.CheckParameterSyntax(new_filled_params)
2441 new_mode = new_filled_params[constants.NIC_MODE]
2442 if new_mode == constants.NIC_MODE_BRIDGED:
2443 bridge = new_filled_params[constants.NIC_LINK]
2444 msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
2446 msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
2448 self.warn.append(msg)
2450 raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
2452 elif new_mode == constants.NIC_MODE_ROUTED:
2453 ip = params.get(constants.INIC_IP, old_ip)
2455 raise errors.OpPrereqError("Cannot set the NIC IP address to None"
2456 " on a routed NIC", errors.ECODE_INVAL)
2458 elif new_mode == constants.NIC_MODE_OVS:
2459 # TODO: check OVS link
2460 self.LogInfo("OVS links are currently not checked for correctness")
2462 if constants.INIC_MAC in params:
2463 mac = params[constants.INIC_MAC]
2465 raise errors.OpPrereqError("Cannot unset the NIC MAC address",
2467 elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2468 # otherwise generate the MAC address
2469 params[constants.INIC_MAC] = \
2470 self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2472 # or validate/reserve the current one
2474 self.cfg.ReserveMAC(mac, self.proc.GetECId())
2475 except errors.ReservationError:
2476 raise errors.OpPrereqError("MAC address '%s' already in use"
2477 " in cluster" % mac,
2478 errors.ECODE_NOTUNIQUE)
2479 elif new_net_uuid != old_net_uuid:
2481 def get_net_prefix(net_uuid):
2484 nobj = self.cfg.GetNetwork(net_uuid)
2485 mac_prefix = nobj.mac_prefix
2489 new_prefix = get_net_prefix(new_net_uuid)
2490 old_prefix = get_net_prefix(old_net_uuid)
2491 if old_prefix != new_prefix:
2492 params[constants.INIC_MAC] = \
2493 self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2495 # if there is a change in (ip, network) tuple
2496 new_ip = params.get(constants.INIC_IP, old_ip)
2497 if (new_ip, new_net_uuid) != (old_ip, old_net_uuid):
2499 # if IP is pool then require a network and generate one IP
2500 if new_ip.lower() == constants.NIC_IP_POOL:
2503 new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId())
2504 except errors.ReservationError:
2505 raise errors.OpPrereqError("Unable to get a free IP"
2506 " from the address pool",
2508 self.LogInfo("Chose IP %s from network %s",
2511 params[constants.INIC_IP] = new_ip
2513 raise errors.OpPrereqError("ip=pool, but no network found",
2515 # Reserve new IP if in the new network if any
2518 self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId())
2519 self.LogInfo("Reserving IP %s in network %s",
2520 new_ip, new_net_obj.name)
2521 except errors.ReservationError:
2522 raise errors.OpPrereqError("IP %s not available in network %s" %
2523 (new_ip, new_net_obj.name),
2524 errors.ECODE_NOTUNIQUE)
2525 # new network is None so check if new IP is a conflicting IP
2526 elif self.op.conflicts_check:
2527 _CheckForConflictingIp(self, new_ip, pnode)
2529 # release old IP if old network is not None
2530 if old_ip and old_net_uuid:
2532 self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId())
2533 except errors.AddressPoolError:
2534 logging.warning("Release IP %s not contained in network %s",
2535 old_ip, old_net_obj.name)
2537 # there are no changes in (ip, network) tuple and old network is not None
2538 elif (old_net_uuid is not None and
2539 (req_link is not None or req_mode is not None)):
2540 raise errors.OpPrereqError("Not allowed to change link or mode of"
2541 " a NIC that is connected to a network",
2544 private.params = new_params
2545 private.filled = new_filled_params
2547 def _PreCheckDiskTemplate(self, pnode_info):
2548 """CheckPrereq checks related to a new disk template."""
2549 # Arguments are passed to avoid configuration lookups
2550 instance = self.instance
2551 pnode = instance.primary_node
2552 cluster = self.cluster
2553 if instance.disk_template == self.op.disk_template:
2554 raise errors.OpPrereqError("Instance already has disk template %s" %
2555 instance.disk_template, errors.ECODE_INVAL)
2557 if (instance.disk_template,
2558 self.op.disk_template) not in self._DISK_CONVERSIONS:
2559 raise errors.OpPrereqError("Unsupported disk template conversion from"
2560 " %s to %s" % (instance.disk_template,
2561 self.op.disk_template),
2563 CheckInstanceState(self, instance, INSTANCE_DOWN,
2564 msg="cannot change disk template")
2565 if self.op.disk_template in constants.DTS_INT_MIRROR:
2566 if self.op.remote_node == pnode:
2567 raise errors.OpPrereqError("Given new secondary node %s is the same"
2568 " as the primary node of the instance" %
2569 self.op.remote_node, errors.ECODE_STATE)
2570 CheckNodeOnline(self, self.op.remote_node)
2571 CheckNodeNotDrained(self, self.op.remote_node)
2572 # FIXME: here we assume that the old instance type is DT_PLAIN
2573 assert instance.disk_template == constants.DT_PLAIN
2574 disks = [{constants.IDISK_SIZE: d.size,
2575 constants.IDISK_VG: d.logical_id[0]}
2576 for d in instance.disks]
2577 required = ComputeDiskSizePerVG(self.op.disk_template, disks)
2578 CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
2580 snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
2581 snode_group = self.cfg.GetNodeGroup(snode_info.group)
2582 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2584 CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info, self.cfg,
2585 ignore=self.op.ignore_ipolicy)
2586 if pnode_info.group != snode_info.group:
2587 self.LogWarning("The primary and secondary nodes are in two"
2588 " different node groups; the disk parameters"
2589 " from the first disk's node group will be"
2592 if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
2593 # Make sure none of the nodes require exclusive storage
2594 nodes = [pnode_info]
2595 if self.op.disk_template in constants.DTS_INT_MIRROR:
2597 nodes.append(snode_info)
2598 has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
2599 if compat.any(map(has_es, nodes)):
2600 errmsg = ("Cannot convert disk template from %s to %s when exclusive"
2601 " storage is enabled" % (instance.disk_template,
2602 self.op.disk_template))
2603 raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
2605 def CheckPrereq(self):
2606 """Check prerequisites.
2608 This only checks the instance list against the existing names.
2611 assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
2612 instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
2614 cluster = self.cluster = self.cfg.GetClusterInfo()
2615 assert self.instance is not None, \
2616 "Cannot retrieve locked instance %s" % self.op.instance_name
2618 pnode = instance.primary_node
2622 if (self.op.pnode is not None and self.op.pnode != pnode and
2624 # verify that the instance is not up
2625 instance_info = self.rpc.call_instance_info(pnode, instance.name,
2626 instance.hypervisor)
2627 if instance_info.fail_msg:
2628 self.warn.append("Can't get instance runtime information: %s" %
2629 instance_info.fail_msg)
2630 elif instance_info.payload:
2631 raise errors.OpPrereqError("Instance is still running on %s" % pnode,
2634 assert pnode in self.owned_locks(locking.LEVEL_NODE)
2635 nodelist = list(instance.all_nodes)
2636 pnode_info = self.cfg.GetNodeInfo(pnode)
2637 self.diskparams = self.cfg.GetInstanceDiskParams(instance)
2639 #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
2640 assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
2641 group_info = self.cfg.GetNodeGroup(pnode_info.group)
2643 # dictionary with instance information after the modification
2646 # Check disk modifications. This is done here and not in CheckArguments
2647 # (as with NICs), because we need to know the instance's disk template
2648 if instance.disk_template == constants.DT_EXT:
2649 self._CheckMods("disk", self.op.disks, {},
2650 self._VerifyDiskModification)
2652 self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
2653 self._VerifyDiskModification)
2655 # Prepare disk/NIC modifications
2656 self.diskmod = _PrepareContainerMods(self.op.disks, None)
2657 self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
2659 # Check the validity of the `provider' parameter
2660 if instance.disk_template in constants.DT_EXT:
2661 for mod in self.diskmod:
2662 ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2663 if mod[0] == constants.DDM_ADD:
2664 if ext_provider is None:
2665 raise errors.OpPrereqError("Instance template is '%s' and parameter"
2666 " '%s' missing, during disk add" %
2668 constants.IDISK_PROVIDER),
2670 elif mod[0] == constants.DDM_MODIFY:
2672 raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
2674 constants.IDISK_PROVIDER,
2677 for mod in self.diskmod:
2678 ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2679 if ext_provider is not None:
2680 raise errors.OpPrereqError("Parameter '%s' is only valid for"
2681 " instances of type '%s'" %
2682 (constants.IDISK_PROVIDER,
2687 if self.op.os_name and not self.op.force:
2688 CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
2689 self.op.force_variant)
2690 instance_os = self.op.os_name
2692 instance_os = instance.os
2694 assert not (self.op.disk_template and self.op.disks), \
2695 "Can't modify disk template and apply disk changes at the same time"
2697 if self.op.disk_template:
2698 self._PreCheckDiskTemplate(pnode_info)
2700 # hvparams processing
2701 if self.op.hvparams:
2702 hv_type = instance.hypervisor
2703 i_hvdict = GetUpdatedParams(instance.hvparams, self.op.hvparams)
2704 utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
2705 hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
2708 hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
2709 CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
2710 self.hv_proposed = self.hv_new = hv_new # the new actual values
2711 self.hv_inst = i_hvdict # the new dict (without defaults)
2713 self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
2715 self.hv_new = self.hv_inst = {}
2717 # beparams processing
2718 if self.op.beparams:
2719 i_bedict = GetUpdatedParams(instance.beparams, self.op.beparams,
2721 objects.UpgradeBeParams(i_bedict)
2722 utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
2723 be_new = cluster.SimpleFillBE(i_bedict)
2724 self.be_proposed = self.be_new = be_new # the new actual values
2725 self.be_inst = i_bedict # the new dict (without defaults)
2727 self.be_new = self.be_inst = {}
2728 self.be_proposed = cluster.SimpleFillBE(instance.beparams)
2729 be_old = cluster.FillBE(instance)
2731 # CPU param validation -- checking every time a parameter is
2732 # changed to cover all cases where either CPU mask or vcpus have
2734 if (constants.BE_VCPUS in self.be_proposed and
2735 constants.HV_CPU_MASK in self.hv_proposed):
2737 utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
2738 # Verify mask is consistent with number of vCPUs. Can skip this
2739 # test if only 1 entry in the CPU mask, which means same mask
2740 # is applied to all vCPUs.
2741 if (len(cpu_list) > 1 and
2742 len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
2743 raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
2745 (self.be_proposed[constants.BE_VCPUS],
2746 self.hv_proposed[constants.HV_CPU_MASK]),
2749 # Only perform this test if a new CPU mask is given
2750 if constants.HV_CPU_MASK in self.hv_new:
2751 # Calculate the largest CPU number requested
2752 max_requested_cpu = max(map(max, cpu_list))
2753 # Check that all of the instance's nodes have enough physical CPUs to
2754 # satisfy the requested CPU mask
2755 _CheckNodesPhysicalCPUs(self, instance.all_nodes,
2756 max_requested_cpu + 1, instance.hypervisor)
2758 # osparams processing
2759 if self.op.osparams:
2760 i_osdict = GetUpdatedParams(instance.osparams, self.op.osparams)
2761 CheckOSParams(self, True, nodelist, instance_os, i_osdict)
2762 self.os_inst = i_osdict # the new dict (without defaults)
2766 #TODO(dynmem): do the appropriate check involving MINMEM
2767 if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
2768 be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
2769 mem_check_list = [pnode]
2770 if be_new[constants.BE_AUTO_BALANCE]:
2771 # either we changed auto_balance to yes or it was from before
2772 mem_check_list.extend(instance.secondary_nodes)
2773 instance_info = self.rpc.call_instance_info(pnode, instance.name,
2774 instance.hypervisor)
2775 nodeinfo = self.rpc.call_node_info(mem_check_list, None,
2776 [instance.hypervisor], False)
2777 pninfo = nodeinfo[pnode]
2778 msg = pninfo.fail_msg
2780 # Assume the primary node is unreachable and go ahead
2781 self.warn.append("Can't get info from primary node %s: %s" %
2784 (_, _, (pnhvinfo, )) = pninfo.payload
2785 if not isinstance(pnhvinfo.get("memory_free", None), int):
2786 self.warn.append("Node data from primary node %s doesn't contain"
2787 " free memory information" % pnode)
2788 elif instance_info.fail_msg:
2789 self.warn.append("Can't get instance runtime information: %s" %
2790 instance_info.fail_msg)
2792 if instance_info.payload:
2793 current_mem = int(instance_info.payload["memory"])
2795 # Assume instance not running
2796 # (there is a slight race condition here, but it's not very
2797 # probable, and we have no other way to check)
2798 # TODO: Describe race condition
2800 #TODO(dynmem): do the appropriate check involving MINMEM
2801 miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
2802 pnhvinfo["memory_free"])
2804 raise errors.OpPrereqError("This change will prevent the instance"
2805 " from starting, due to %d MB of memory"
2806 " missing on its primary node" %
2807 miss_mem, errors.ECODE_NORES)
2809 if be_new[constants.BE_AUTO_BALANCE]:
2810 for node, nres in nodeinfo.items():
2811 if node not in instance.secondary_nodes:
2813 nres.Raise("Can't get info from secondary node %s" % node,
2814 prereq=True, ecode=errors.ECODE_STATE)
2815 (_, _, (nhvinfo, )) = nres.payload
2816 if not isinstance(nhvinfo.get("memory_free", None), int):
2817 raise errors.OpPrereqError("Secondary node %s didn't return free"
2818 " memory information" % node,
2820 #TODO(dynmem): do the appropriate check involving MINMEM
2821 elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
2822 raise errors.OpPrereqError("This change will prevent the instance"
2823 " from failover to its secondary node"
2824 " %s, due to not enough memory" % node,
2827 if self.op.runtime_mem:
2828 remote_info = self.rpc.call_instance_info(instance.primary_node,
2830 instance.hypervisor)
2831 remote_info.Raise("Error checking node %s" % instance.primary_node)
2832 if not remote_info.payload: # not running already
2833 raise errors.OpPrereqError("Instance %s is not running" %
2834 instance.name, errors.ECODE_STATE)
2836 current_memory = remote_info.payload["memory"]
2837 if (not self.op.force and
2838 (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
2839 self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
2840 raise errors.OpPrereqError("Instance %s must have memory between %d"
2841 " and %d MB of memory unless --force is"
2844 self.be_proposed[constants.BE_MINMEM],
2845 self.be_proposed[constants.BE_MAXMEM]),
2848 delta = self.op.runtime_mem - current_memory
2850 CheckNodeFreeMemory(self, instance.primary_node,
2851 "ballooning memory for instance %s" %
2852 instance.name, delta, instance.hypervisor)
2854 if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
2855 raise errors.OpPrereqError("Disk operations not supported for"
2856 " diskless instances", errors.ECODE_INVAL)
2858 def _PrepareNicCreate(_, params, private):
2859 self._PrepareNicModification(params, private, None, None,
2863 def _PrepareNicMod(_, nic, params, private):
2864 self._PrepareNicModification(params, private, nic.ip, nic.network,
2865 nic.nicparams, cluster, pnode)
2868 def _PrepareNicRemove(_, params, __):
2870 net = params.network
2871 if net is not None and ip is not None:
2872 self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
2874 # Verify NIC changes (operating on copy)
2875 nics = instance.nics[:]
2876 _ApplyContainerMods("NIC", nics, None, self.nicmod,
2877 _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
2878 if len(nics) > constants.MAX_NICS:
2879 raise errors.OpPrereqError("Instance has too many network interfaces"
2880 " (%d), cannot add more" % constants.MAX_NICS,
2883 def _PrepareDiskMod(_, disk, params, __):
2884 disk.name = params.get(constants.IDISK_NAME, None)
2886 # Verify disk changes (operating on a copy)
2887 disks = copy.deepcopy(instance.disks)
2888 _ApplyContainerMods("disk", disks, None, self.diskmod, None,
2889 _PrepareDiskMod, None)
2890 utils.ValidateDeviceNames("disk", disks)
2891 if len(disks) > constants.MAX_DISKS:
2892 raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
2893 " more" % constants.MAX_DISKS,
2895 disk_sizes = [disk.size for disk in instance.disks]
2896 disk_sizes.extend(params["size"] for (op, idx, params, private) in
2897 self.diskmod if op == constants.DDM_ADD)
2898 ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
2899 ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
2901 if self.op.offline is not None and self.op.offline:
2902 CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE,
2903 msg="can't change to offline")
2905 # Pre-compute NIC changes (necessary to use result in hooks)
2906 self._nic_chgdesc = []
2908 # Operate on copies as this is still in prereq
2909 nics = [nic.Copy() for nic in instance.nics]
2910 _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
2911 self._CreateNewNic, self._ApplyNicMods, None)
2912 # Verify that NIC names are unique and valid
2913 utils.ValidateDeviceNames("NIC", nics)
2914 self._new_nics = nics
2915 ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
2917 self._new_nics = None
2918 ispec[constants.ISPEC_NIC_COUNT] = len(instance.nics)
2920 if not self.op.ignore_ipolicy:
2921 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2924 # Fill ispec with backend parameters
2925 ispec[constants.ISPEC_SPINDLE_USE] = \
2926 self.be_new.get(constants.BE_SPINDLE_USE, None)
2927 ispec[constants.ISPEC_CPU_COUNT] = self.be_new.get(constants.BE_VCPUS,
2930 # Copy ispec to verify parameters with min/max values separately
2931 if self.op.disk_template:
2932 new_disk_template = self.op.disk_template
2934 new_disk_template = instance.disk_template
2935 ispec_max = ispec.copy()
2936 ispec_max[constants.ISPEC_MEM_SIZE] = \
2937 self.be_new.get(constants.BE_MAXMEM, None)
2938 res_max = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_max,
2940 ispec_min = ispec.copy()
2941 ispec_min[constants.ISPEC_MEM_SIZE] = \
2942 self.be_new.get(constants.BE_MINMEM, None)
2943 res_min = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
2946 if (res_max or res_min):
2947 # FIXME: Improve error message by including information about whether
2948 # the upper or lower limit of the parameter fails the ipolicy.
2949 msg = ("Instance allocation to group %s (%s) violates policy: %s" %
2950 (group_info, group_info.name,
2951 utils.CommaJoin(set(res_max + res_min))))
2952 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2954 def _ConvertPlainToDrbd(self, feedback_fn):
2955 """Converts an instance from plain to drbd.
2958 feedback_fn("Converting template to drbd")
2959 instance = self.instance
2960 pnode = instance.primary_node
2961 snode = self.op.remote_node
2963 assert instance.disk_template == constants.DT_PLAIN
2965 # create a fake disk info for _GenerateDiskTemplate
2966 disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
2967 constants.IDISK_VG: d.logical_id[0],
2968 constants.IDISK_NAME: d.name}
2969 for d in instance.disks]
2970 new_disks = GenerateDiskTemplate(self, self.op.disk_template,
2971 instance.name, pnode, [snode],
2972 disk_info, None, None, 0, feedback_fn,
2974 anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
2976 p_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, pnode)
2977 s_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, snode)
2978 info = GetInstanceInfoText(instance)
2979 feedback_fn("Creating additional volumes...")
2980 # first, create the missing data and meta devices
2981 for disk in anno_disks:
2982 # unfortunately this is... not too nice
2983 CreateSingleBlockDev(self, pnode, instance, disk.children[1],
2984 info, True, p_excl_stor)
2985 for child in disk.children:
2986 CreateSingleBlockDev(self, snode, instance, child, info, True,
2988 # at this stage, all new LVs have been created, we can rename the
2990 feedback_fn("Renaming original volumes...")
2991 rename_list = [(o, n.children[0].logical_id)
2992 for (o, n) in zip(instance.disks, new_disks)]
2993 result = self.rpc.call_blockdev_rename(pnode, rename_list)
2994 result.Raise("Failed to rename original LVs")
2996 feedback_fn("Initializing DRBD devices...")
2997 # all child devices are in place, we can now create the DRBD devices
2999 for disk in anno_disks:
3000 for (node, excl_stor) in [(pnode, p_excl_stor), (snode, s_excl_stor)]:
3001 f_create = node == pnode
3002 CreateSingleBlockDev(self, node, instance, disk, info, f_create,
3004 except errors.GenericError, e:
3005 feedback_fn("Initializing of DRBD devices failed;"
3006 " renaming back original volumes...")
3007 for disk in new_disks:
3008 self.cfg.SetDiskID(disk, pnode)
3009 rename_back_list = [(n.children[0], o.logical_id)
3010 for (n, o) in zip(new_disks, instance.disks)]
3011 result = self.rpc.call_blockdev_rename(pnode, rename_back_list)
3012 result.Raise("Failed to rename LVs back after error %s" % str(e))
3015 # at this point, the instance has been modified
3016 instance.disk_template = constants.DT_DRBD8
3017 instance.disks = new_disks
3018 self.cfg.Update(instance, feedback_fn)
3020 # Release node locks while waiting for sync
3021 ReleaseLocks(self, locking.LEVEL_NODE)
3023 # disks are created, waiting for sync
3024 disk_abort = not WaitForSync(self, instance,
3025 oneshot=not self.op.wait_for_sync)
3027 raise errors.OpExecError("There are some degraded disks for"
3028 " this instance, please cleanup manually")
3030 # Node resource locks will be released by caller
3032 def _ConvertDrbdToPlain(self, feedback_fn):
3033 """Converts an instance from drbd to plain.
3036 instance = self.instance
3038 assert len(instance.secondary_nodes) == 1
3039 assert instance.disk_template == constants.DT_DRBD8
3041 pnode = instance.primary_node
3042 snode = instance.secondary_nodes[0]
3043 feedback_fn("Converting template to plain")
3045 old_disks = AnnotateDiskParams(instance, instance.disks, self.cfg)
3046 new_disks = [d.children[0] for d in instance.disks]
3048 # copy over size, mode and name
3049 for parent, child in zip(old_disks, new_disks):
3050 child.size = parent.size
3051 child.mode = parent.mode
3052 child.name = parent.name
3054 # this is a DRBD disk, return its port to the pool
3055 # NOTE: this must be done right before the call to cfg.Update!
3056 for disk in old_disks:
3057 tcp_port = disk.logical_id[2]
3058 self.cfg.AddTcpUdpPort(tcp_port)
3060 # update instance structure
3061 instance.disks = new_disks
3062 instance.disk_template = constants.DT_PLAIN
3063 _UpdateIvNames(0, instance.disks)
3064 self.cfg.Update(instance, feedback_fn)
3066 # Release locks in case removing disks takes a while
3067 ReleaseLocks(self, locking.LEVEL_NODE)
3069 feedback_fn("Removing volumes on the secondary node...")
3070 for disk in old_disks:
3071 self.cfg.SetDiskID(disk, snode)
3072 msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
3074 self.LogWarning("Could not remove block device %s on node %s,"
3075 " continuing anyway: %s", disk.iv_name, snode, msg)
3077 feedback_fn("Removing unneeded volumes on the primary node...")
3078 for idx, disk in enumerate(old_disks):
3079 meta = disk.children[1]
3080 self.cfg.SetDiskID(meta, pnode)
3081 msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
3083 self.LogWarning("Could not remove metadata for disk %d on node %s,"
3084 " continuing anyway: %s", idx, pnode, msg)
3086 def _CreateNewDisk(self, idx, params, _):
3087 """Creates a new disk.
3090 instance = self.instance
3093 if instance.disk_template in constants.DTS_FILEBASED:
3094 (file_driver, file_path) = instance.disks[0].logical_id
3095 file_path = os.path.dirname(file_path)
3097 file_driver = file_path = None
3100 GenerateDiskTemplate(self, instance.disk_template, instance.name,
3101 instance.primary_node, instance.secondary_nodes,
3102 [params], file_path, file_driver, idx,
3103 self.Log, self.diskparams)[0]
3105 info = GetInstanceInfoText(instance)
3107 logging.info("Creating volume %s for instance %s",
3108 disk.iv_name, instance.name)
3109 # Note: this needs to be kept in sync with _CreateDisks
3111 for node in instance.all_nodes:
3112 f_create = (node == instance.primary_node)
3114 CreateBlockDev(self, node, instance, disk, f_create, info, f_create)
3115 except errors.OpExecError, err:
3116 self.LogWarning("Failed to create volume %s (%s) on node '%s': %s",
3117 disk.iv_name, disk, node, err)
3119 if self.cluster.prealloc_wipe_disks:
3121 WipeDisks(self, instance,
3122 disks=[(idx, disk, 0)])
3125 ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
3129 def _ModifyDisk(idx, disk, params, _):
3134 mode = params.get(constants.IDISK_MODE, None)
3137 changes.append(("disk.mode/%d" % idx, disk.mode))
3139 name = params.get(constants.IDISK_NAME, None)
3141 changes.append(("disk.name/%d" % idx, disk.name))
3145 def _RemoveDisk(self, idx, root, _):
3149 (anno_disk,) = AnnotateDiskParams(self.instance, [root], self.cfg)
3150 for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
3151 self.cfg.SetDiskID(disk, node)
3152 msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
3154 self.LogWarning("Could not remove disk/%d on node '%s': %s,"
3155 " continuing anyway", idx, node, msg)
3157 # if this is a DRBD disk, return its port to the pool
3158 if root.dev_type in constants.LDS_DRBD:
3159 self.cfg.AddTcpUdpPort(root.logical_id[2])
3161 def _CreateNewNic(self, idx, params, private):
3162 """Creates data structure for a new network interface.
3165 mac = params[constants.INIC_MAC]
3166 ip = params.get(constants.INIC_IP, None)
3167 net = params.get(constants.INIC_NETWORK, None)
3168 name = params.get(constants.INIC_NAME, None)
3169 net_uuid = self.cfg.LookupNetwork(net)
3170 #TODO: not private.filled?? can a nic have no nicparams??
3171 nicparams = private.filled
3172 nobj = objects.NIC(mac=mac, ip=ip, network=net_uuid, name=name,
3173 nicparams=nicparams)
3174 nobj.uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
3178 "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" %
3179 (mac, ip, private.filled[constants.NIC_MODE],
3180 private.filled[constants.NIC_LINK],
3184 def _ApplyNicMods(self, idx, nic, params, private):
3185 """Modifies a network interface.
3190 for key in [constants.INIC_MAC, constants.INIC_IP, constants.INIC_NAME]:
3192 changes.append(("nic.%s/%d" % (key, idx), params[key]))
3193 setattr(nic, key, params[key])
3195 new_net = params.get(constants.INIC_NETWORK, nic.network)
3196 new_net_uuid = self.cfg.LookupNetwork(new_net)
3197 if new_net_uuid != nic.network:
3198 changes.append(("nic.network/%d" % idx, new_net))
3199 nic.network = new_net_uuid
3202 nic.nicparams = private.filled
3204 for (key, val) in nic.nicparams.items():
3205 changes.append(("nic.%s/%d" % (key, idx), val))
3209 def Exec(self, feedback_fn):
3210 """Modifies an instance.
3212 All parameters take effect only at the next restart of the instance.
3215 # Process here the warnings from CheckPrereq, as we don't have a
3216 # feedback_fn there.
3217 # TODO: Replace with self.LogWarning
3218 for warn in self.warn:
3219 feedback_fn("WARNING: %s" % warn)
3221 assert ((self.op.disk_template is None) ^
3222 bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
3223 "Not owning any node resource locks"
3226 instance = self.instance
3230 instance.primary_node = self.op.pnode
3233 if self.op.runtime_mem:
3234 rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
3236 self.op.runtime_mem)
3237 rpcres.Raise("Cannot modify instance runtime memory")
3238 result.append(("runtime_memory", self.op.runtime_mem))
3240 # Apply disk changes
3241 _ApplyContainerMods("disk", instance.disks, result, self.diskmod,
3242 self._CreateNewDisk, self._ModifyDisk,
3244 _UpdateIvNames(0, instance.disks)
3246 if self.op.disk_template:
3248 check_nodes = set(instance.all_nodes)
3249 if self.op.remote_node:
3250 check_nodes.add(self.op.remote_node)
3251 for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
3252 owned = self.owned_locks(level)
3253 assert not (check_nodes - owned), \
3254 ("Not owning the correct locks, owning %r, expected at least %r" %
3255 (owned, check_nodes))
3257 r_shut = ShutdownInstanceDisks(self, instance)
3259 raise errors.OpExecError("Cannot shutdown instance disks, unable to"
3260 " proceed with disk template conversion")
3261 mode = (instance.disk_template, self.op.disk_template)
3263 self._DISK_CONVERSIONS[mode](self, feedback_fn)
3265 self.cfg.ReleaseDRBDMinors(instance.name)
3267 result.append(("disk_template", self.op.disk_template))
3269 assert instance.disk_template == self.op.disk_template, \
3270 ("Expected disk template '%s', found '%s'" %
3271 (self.op.disk_template, instance.disk_template))
3273 # Release node and resource locks if there are any (they might already have
3274 # been released during disk conversion)
3275 ReleaseLocks(self, locking.LEVEL_NODE)
3276 ReleaseLocks(self, locking.LEVEL_NODE_RES)
3279 if self._new_nics is not None:
3280 instance.nics = self._new_nics
3281 result.extend(self._nic_chgdesc)
3284 if self.op.hvparams:
3285 instance.hvparams = self.hv_inst
3286 for key, val in self.op.hvparams.iteritems():
3287 result.append(("hv/%s" % key, val))
3290 if self.op.beparams:
3291 instance.beparams = self.be_inst
3292 for key, val in self.op.beparams.iteritems():
3293 result.append(("be/%s" % key, val))
3297 instance.os = self.op.os_name
3300 if self.op.osparams:
3301 instance.osparams = self.os_inst
3302 for key, val in self.op.osparams.iteritems():
3303 result.append(("os/%s" % key, val))
3305 if self.op.offline is None:
3308 elif self.op.offline:
3309 # Mark instance as offline
3310 self.cfg.MarkInstanceOffline(instance.name)
3311 result.append(("admin_state", constants.ADMINST_OFFLINE))
3313 # Mark instance as online, but stopped
3314 self.cfg.MarkInstanceDown(instance.name)
3315 result.append(("admin_state", constants.ADMINST_DOWN))
3317 self.cfg.Update(instance, feedback_fn, self.proc.GetECId())
3319 assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
3320 self.owned_locks(locking.LEVEL_NODE)), \
3321 "All node locks should have been released by now"
3325 _DISK_CONVERSIONS = {
3326 (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
3327 (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
3331 class LUInstanceChangeGroup(LogicalUnit):
3332 HPATH = "instance-change-group"
3333 HTYPE = constants.HTYPE_INSTANCE
3336 def ExpandNames(self):
3337 self.share_locks = ShareAll()
3339 self.needed_locks = {
3340 locking.LEVEL_NODEGROUP: [],
3341 locking.LEVEL_NODE: [],
3342 locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
3345 self._ExpandAndLockInstance()
3347 if self.op.target_groups:
3348 self.req_target_uuids = map(self.cfg.LookupNodeGroup,
3349 self.op.target_groups)
3351 self.req_target_uuids = None
3353 self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
3355 def DeclareLocks(self, level):
3356 if level == locking.LEVEL_NODEGROUP:
3357 assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3359 if self.req_target_uuids:
3360 lock_groups = set(self.req_target_uuids)
3362 # Lock all groups used by instance optimistically; this requires going
3363 # via the node before it's locked, requiring verification later on
3364 instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
3365 lock_groups.update(instance_groups)
3367 # No target groups, need to lock all of them
3368 lock_groups = locking.ALL_SET
3370 self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
3372 elif level == locking.LEVEL_NODE:
3373 if self.req_target_uuids:
3374 # Lock all nodes used by instances
3375 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3376 self._LockInstancesNodes()
3378 # Lock all nodes in all potential target groups
3379 lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
3380 self.cfg.GetInstanceNodeGroups(self.op.instance_name))
3381 member_nodes = [node_name
3382 for group in lock_groups
3383 for node_name in self.cfg.GetNodeGroup(group).members]
3384 self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3386 # Lock all nodes as all groups are potential targets
3387 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3389 def CheckPrereq(self):
3390 owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3391 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3392 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3394 assert (self.req_target_uuids is None or
3395 owned_groups.issuperset(self.req_target_uuids))
3396 assert owned_instances == set([self.op.instance_name])
3398 # Get instance information
3399 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3401 # Check if node groups for locked instance are still correct
3402 assert owned_nodes.issuperset(self.instance.all_nodes), \
3403 ("Instance %s's nodes changed while we kept the lock" %
3404 self.op.instance_name)
3406 inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
3409 if self.req_target_uuids:
3410 # User requested specific target groups
3411 self.target_uuids = frozenset(self.req_target_uuids)
3413 # All groups except those used by the instance are potential targets
3414 self.target_uuids = owned_groups - inst_groups
3416 conflicting_groups = self.target_uuids & inst_groups
3417 if conflicting_groups:
3418 raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
3419 " used by the instance '%s'" %
3420 (utils.CommaJoin(conflicting_groups),
3421 self.op.instance_name),
3424 if not self.target_uuids:
3425 raise errors.OpPrereqError("There are no possible target groups",
3428 def BuildHooksEnv(self):
3432 assert self.target_uuids
3435 "TARGET_GROUPS": " ".join(self.target_uuids),
3438 env.update(BuildInstanceHookEnvByObject(self, self.instance))
3442 def BuildHooksNodes(self):
3443 """Build hooks nodes.
3446 mn = self.cfg.GetMasterNode()
3449 def Exec(self, feedback_fn):
3450 instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
3452 assert instances == [self.op.instance_name], "Instance not locked"
3454 req = iallocator.IAReqGroupChange(instances=instances,
3455 target_groups=list(self.target_uuids))
3456 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
3458 ial.Run(self.op.iallocator)
3461 raise errors.OpPrereqError("Can't compute solution for changing group of"
3462 " instance '%s' using iallocator '%s': %s" %
3463 (self.op.instance_name, self.op.iallocator,
3464 ial.info), errors.ECODE_NORES)
3466 jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
3468 self.LogInfo("Iallocator returned %s job(s) for changing group of"
3469 " instance '%s'", len(jobs), self.op.instance_name)
3471 return ResultWithJobs(jobs)