e6abee26e39c21b7cb0be8011605b8248d2b473b
[ganeti-github.git] / lib / cmdlib / instance.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Logical units dealing with instances."""
23
24 import OpenSSL
25 import copy
26 import logging
27 import os
28
29 from ganeti import compat
30 from ganeti import constants
31 from ganeti import errors
32 from ganeti import ht
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
43
44 from ganeti.cmdlib.base import NoHooksLU, LogicalUnit, ResultWithJobs
45
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, WipeOrCleanupDisks, WaitForSync, \
55 IsExclusiveStorageEnabledNodeName, CreateSingleBlockDev, ComputeDisks, \
56 CheckRADOSFreeSpace, ComputeDiskSizePerVG, GenerateDiskTemplate, \
57 StartInstanceDisks, ShutdownInstanceDisks, AssembleInstanceDisks
58 from ganeti.cmdlib.instance_utils import BuildInstanceHookEnvByObject, \
59 GetClusterDomainSecret, BuildInstanceHookEnv, NICListToTuple, \
60 NICToTuple, CheckNodeNotDrained, RemoveInstance, CopyLockList, \
61 ReleaseLocks, CheckNodeVmCapable, CheckTargetNodeIPolicy, \
62 GetInstanceInfoText, RemoveDisks, CheckNodeFreeMemory, \
63 CheckInstanceBridgesExist, CheckNicsBridgesExist, CheckNodeHasOS
64
65 import ganeti.masterd.instance
66
67
68 #: Type description for changes as returned by L{_ApplyContainerMods}'s
69 #: callbacks
70 _TApplyContModsCbChanges = \
71 ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
72 ht.TNonEmptyString,
73 ht.TAny,
74 ])))
75
76
77 def _CheckHostnameSane(lu, name):
78 """Ensures that a given hostname resolves to a 'sane' name.
79
80 The given name is required to be a prefix of the resolved hostname,
81 to prevent accidental mismatches.
82
83 @param lu: the logical unit on behalf of which we're checking
84 @param name: the name we should resolve and check
85 @return: the resolved hostname object
86
87 """
88 hostname = netutils.GetHostname(name=name)
89 if hostname.name != name:
90 lu.LogInfo("Resolved given name '%s' to '%s'", name, hostname.name)
91 if not utils.MatchNameComponent(name, [hostname.name]):
92 raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
93 " same as given hostname '%s'") %
94 (hostname.name, name), errors.ECODE_INVAL)
95 return hostname
96
97
98 def _CheckOpportunisticLocking(op):
99 """Generate error if opportunistic locking is not possible.
100
101 """
102 if op.opportunistic_locking and not op.iallocator:
103 raise errors.OpPrereqError("Opportunistic locking is only available in"
104 " combination with an instance allocator",
105 errors.ECODE_INVAL)
106
107
108 def _CreateInstanceAllocRequest(op, disks, nics, beparams, node_whitelist):
109 """Wrapper around IAReqInstanceAlloc.
110
111 @param op: The instance opcode
112 @param disks: The computed disks
113 @param nics: The computed nics
114 @param beparams: The full filled beparams
115 @param node_whitelist: List of nodes which should appear as online to the
116 allocator (unless the node is already marked offline)
117
118 @returns: A filled L{iallocator.IAReqInstanceAlloc}
119
120 """
121 spindle_use = beparams[constants.BE_SPINDLE_USE]
122 return iallocator.IAReqInstanceAlloc(name=op.instance_name,
123 disk_template=op.disk_template,
124 tags=op.tags,
125 os=op.os_type,
126 vcpus=beparams[constants.BE_VCPUS],
127 memory=beparams[constants.BE_MAXMEM],
128 spindle_use=spindle_use,
129 disks=disks,
130 nics=[n.ToDict() for n in nics],
131 hypervisor=op.hypervisor,
132 node_whitelist=node_whitelist)
133
134
135 def _ComputeFullBeParams(op, cluster):
136 """Computes the full beparams.
137
138 @param op: The instance opcode
139 @param cluster: The cluster config object
140
141 @return: The fully filled beparams
142
143 """
144 default_beparams = cluster.beparams[constants.PP_DEFAULT]
145 for param, value in op.beparams.iteritems():
146 if value == constants.VALUE_AUTO:
147 op.beparams[param] = default_beparams[param]
148 objects.UpgradeBeParams(op.beparams)
149 utils.ForceDictType(op.beparams, constants.BES_PARAMETER_TYPES)
150 return cluster.SimpleFillBE(op.beparams)
151
152
153 def _ComputeNics(op, cluster, default_ip, cfg, ec_id):
154 """Computes the nics.
155
156 @param op: The instance opcode
157 @param cluster: Cluster configuration object
158 @param default_ip: The default ip to assign
159 @param cfg: An instance of the configuration object
160 @param ec_id: Execution context ID
161
162 @returns: The build up nics
163
164 """
165 nics = []
166 for nic in op.nics:
167 nic_mode_req = nic.get(constants.INIC_MODE, None)
168 nic_mode = nic_mode_req
169 if nic_mode is None or nic_mode == constants.VALUE_AUTO:
170 nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
171
172 net = nic.get(constants.INIC_NETWORK, None)
173 link = nic.get(constants.NIC_LINK, None)
174 ip = nic.get(constants.INIC_IP, None)
175
176 if net is None or net.lower() == constants.VALUE_NONE:
177 net = None
178 else:
179 if nic_mode_req is not None or link is not None:
180 raise errors.OpPrereqError("If network is given, no mode or link"
181 " is allowed to be passed",
182 errors.ECODE_INVAL)
183
184 # ip validity checks
185 if ip is None or ip.lower() == constants.VALUE_NONE:
186 nic_ip = None
187 elif ip.lower() == constants.VALUE_AUTO:
188 if not op.name_check:
189 raise errors.OpPrereqError("IP address set to auto but name checks"
190 " have been skipped",
191 errors.ECODE_INVAL)
192 nic_ip = default_ip
193 else:
194 # We defer pool operations until later, so that the iallocator has
195 # filled in the instance's node(s) dimara
196 if ip.lower() == constants.NIC_IP_POOL:
197 if net is None:
198 raise errors.OpPrereqError("if ip=pool, parameter network"
199 " must be passed too",
200 errors.ECODE_INVAL)
201
202 elif not netutils.IPAddress.IsValid(ip):
203 raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
204 errors.ECODE_INVAL)
205
206 nic_ip = ip
207
208 # TODO: check the ip address for uniqueness
209 if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
210 raise errors.OpPrereqError("Routed nic mode requires an ip address",
211 errors.ECODE_INVAL)
212
213 # MAC address verification
214 mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
215 if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
216 mac = utils.NormalizeAndValidateMac(mac)
217
218 try:
219 # TODO: We need to factor this out
220 cfg.ReserveMAC(mac, ec_id)
221 except errors.ReservationError:
222 raise errors.OpPrereqError("MAC address %s already in use"
223 " in cluster" % mac,
224 errors.ECODE_NOTUNIQUE)
225
226 # Build nic parameters
227 nicparams = {}
228 if nic_mode_req:
229 nicparams[constants.NIC_MODE] = nic_mode
230 if link:
231 nicparams[constants.NIC_LINK] = link
232
233 check_params = cluster.SimpleFillNIC(nicparams)
234 objects.NIC.CheckParameterSyntax(check_params)
235 net_uuid = cfg.LookupNetwork(net)
236 name = nic.get(constants.INIC_NAME, None)
237 if name is not None and name.lower() == constants.VALUE_NONE:
238 name = None
239 nic_obj = objects.NIC(mac=mac, ip=nic_ip, name=name,
240 network=net_uuid, nicparams=nicparams)
241 nic_obj.uuid = cfg.GenerateUniqueID(ec_id)
242 nics.append(nic_obj)
243
244 return nics
245
246
247 def _CheckForConflictingIp(lu, ip, node):
248 """In case of conflicting IP address raise error.
249
250 @type ip: string
251 @param ip: IP address
252 @type node: string
253 @param node: node name
254
255 """
256 (conf_net, _) = lu.cfg.CheckIPInNodeGroup(ip, node)
257 if conf_net is not None:
258 raise errors.OpPrereqError(("The requested IP address (%s) belongs to"
259 " network %s, but the target NIC does not." %
260 (ip, conf_net)),
261 errors.ECODE_STATE)
262
263 return (None, None)
264
265
266 def _ComputeIPolicyInstanceSpecViolation(
267 ipolicy, instance_spec, disk_template,
268 _compute_fn=ComputeIPolicySpecViolation):
269 """Compute if instance specs meets the specs of ipolicy.
270
271 @type ipolicy: dict
272 @param ipolicy: The ipolicy to verify against
273 @param instance_spec: dict
274 @param instance_spec: The instance spec to verify
275 @type disk_template: string
276 @param disk_template: the disk template of the instance
277 @param _compute_fn: The function to verify ipolicy (unittest only)
278 @see: L{ComputeIPolicySpecViolation}
279
280 """
281 mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
282 cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
283 disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
284 disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
285 nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
286 spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
287
288 return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
289 disk_sizes, spindle_use, disk_template)
290
291
292 def _CheckOSVariant(os_obj, name):
293 """Check whether an OS name conforms to the os variants specification.
294
295 @type os_obj: L{objects.OS}
296 @param os_obj: OS object to check
297 @type name: string
298 @param name: OS name passed by the user, to check for validity
299
300 """
301 variant = objects.OS.GetVariant(name)
302 if not os_obj.supported_variants:
303 if variant:
304 raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
305 " passed)" % (os_obj.name, variant),
306 errors.ECODE_INVAL)
307 return
308 if not variant:
309 raise errors.OpPrereqError("OS name must include a variant",
310 errors.ECODE_INVAL)
311
312 if variant not in os_obj.supported_variants:
313 raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
314
315
316 class LUInstanceCreate(LogicalUnit):
317 """Create an instance.
318
319 """
320 HPATH = "instance-add"
321 HTYPE = constants.HTYPE_INSTANCE
322 REQ_BGL = False
323
324 def CheckArguments(self):
325 """Check arguments.
326
327 """
328 # do not require name_check to ease forward/backward compatibility
329 # for tools
330 if self.op.no_install and self.op.start:
331 self.LogInfo("No-installation mode selected, disabling startup")
332 self.op.start = False
333 # validate/normalize the instance name
334 self.op.instance_name = \
335 netutils.Hostname.GetNormalizedName(self.op.instance_name)
336
337 if self.op.ip_check and not self.op.name_check:
338 # TODO: make the ip check more flexible and not depend on the name check
339 raise errors.OpPrereqError("Cannot do IP address check without a name"
340 " check", errors.ECODE_INVAL)
341
342 # check nics' parameter names
343 for nic in self.op.nics:
344 utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
345 # check that NIC's parameters names are unique and valid
346 utils.ValidateDeviceNames("NIC", self.op.nics)
347
348 # check that disk's names are unique and valid
349 utils.ValidateDeviceNames("disk", self.op.disks)
350
351 cluster = self.cfg.GetClusterInfo()
352 if not self.op.disk_template in cluster.enabled_disk_templates:
353 raise errors.OpPrereqError("Cannot create an instance with disk template"
354 " '%s', because it is not enabled in the"
355 " cluster. Enabled disk templates are: %s." %
356 (self.op.disk_template,
357 ",".join(cluster.enabled_disk_templates)))
358
359 # check disks. parameter names and consistent adopt/no-adopt strategy
360 has_adopt = has_no_adopt = False
361 for disk in self.op.disks:
362 if self.op.disk_template != constants.DT_EXT:
363 utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
364 if constants.IDISK_ADOPT in disk:
365 has_adopt = True
366 else:
367 has_no_adopt = True
368 if has_adopt and has_no_adopt:
369 raise errors.OpPrereqError("Either all disks are adopted or none is",
370 errors.ECODE_INVAL)
371 if has_adopt:
372 if self.op.disk_template not in constants.DTS_MAY_ADOPT:
373 raise errors.OpPrereqError("Disk adoption is not supported for the"
374 " '%s' disk template" %
375 self.op.disk_template,
376 errors.ECODE_INVAL)
377 if self.op.iallocator is not None:
378 raise errors.OpPrereqError("Disk adoption not allowed with an"
379 " iallocator script", errors.ECODE_INVAL)
380 if self.op.mode == constants.INSTANCE_IMPORT:
381 raise errors.OpPrereqError("Disk adoption not allowed for"
382 " instance import", errors.ECODE_INVAL)
383 else:
384 if self.op.disk_template in constants.DTS_MUST_ADOPT:
385 raise errors.OpPrereqError("Disk template %s requires disk adoption,"
386 " but no 'adopt' parameter given" %
387 self.op.disk_template,
388 errors.ECODE_INVAL)
389
390 self.adopt_disks = has_adopt
391
392 # instance name verification
393 if self.op.name_check:
394 self.hostname1 = _CheckHostnameSane(self, self.op.instance_name)
395 self.op.instance_name = self.hostname1.name
396 # used in CheckPrereq for ip ping check
397 self.check_ip = self.hostname1.ip
398 else:
399 self.check_ip = None
400
401 # file storage checks
402 if (self.op.file_driver and
403 not self.op.file_driver in constants.FILE_DRIVER):
404 raise errors.OpPrereqError("Invalid file driver name '%s'" %
405 self.op.file_driver, errors.ECODE_INVAL)
406
407 if self.op.disk_template == constants.DT_FILE:
408 opcodes.RequireFileStorage()
409 elif self.op.disk_template == constants.DT_SHARED_FILE:
410 opcodes.RequireSharedFileStorage()
411
412 ### Node/iallocator related checks
413 CheckIAllocatorOrNode(self, "iallocator", "pnode")
414
415 if self.op.pnode is not None:
416 if self.op.disk_template in constants.DTS_INT_MIRROR:
417 if self.op.snode is None:
418 raise errors.OpPrereqError("The networked disk templates need"
419 " a mirror node", errors.ECODE_INVAL)
420 elif self.op.snode:
421 self.LogWarning("Secondary node will be ignored on non-mirrored disk"
422 " template")
423 self.op.snode = None
424
425 _CheckOpportunisticLocking(self.op)
426
427 self._cds = GetClusterDomainSecret()
428
429 if self.op.mode == constants.INSTANCE_IMPORT:
430 # On import force_variant must be True, because if we forced it at
431 # initial install, our only chance when importing it back is that it
432 # works again!
433 self.op.force_variant = True
434
435 if self.op.no_install:
436 self.LogInfo("No-installation mode has no effect during import")
437
438 elif self.op.mode == constants.INSTANCE_CREATE:
439 if self.op.os_type is None:
440 raise errors.OpPrereqError("No guest OS specified",
441 errors.ECODE_INVAL)
442 if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
443 raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
444 " installation" % self.op.os_type,
445 errors.ECODE_STATE)
446 if self.op.disk_template is None:
447 raise errors.OpPrereqError("No disk template specified",
448 errors.ECODE_INVAL)
449
450 elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
451 # Check handshake to ensure both clusters have the same domain secret
452 src_handshake = self.op.source_handshake
453 if not src_handshake:
454 raise errors.OpPrereqError("Missing source handshake",
455 errors.ECODE_INVAL)
456
457 errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
458 src_handshake)
459 if errmsg:
460 raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
461 errors.ECODE_INVAL)
462
463 # Load and check source CA
464 self.source_x509_ca_pem = self.op.source_x509_ca
465 if not self.source_x509_ca_pem:
466 raise errors.OpPrereqError("Missing source X509 CA",
467 errors.ECODE_INVAL)
468
469 try:
470 (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
471 self._cds)
472 except OpenSSL.crypto.Error, err:
473 raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
474 (err, ), errors.ECODE_INVAL)
475
476 (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
477 if errcode is not None:
478 raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
479 errors.ECODE_INVAL)
480
481 self.source_x509_ca = cert
482
483 src_instance_name = self.op.source_instance_name
484 if not src_instance_name:
485 raise errors.OpPrereqError("Missing source instance name",
486 errors.ECODE_INVAL)
487
488 self.source_instance_name = \
489 netutils.GetHostname(name=src_instance_name).name
490
491 else:
492 raise errors.OpPrereqError("Invalid instance creation mode %r" %
493 self.op.mode, errors.ECODE_INVAL)
494
495 def ExpandNames(self):
496 """ExpandNames for CreateInstance.
497
498 Figure out the right locks for instance creation.
499
500 """
501 self.needed_locks = {}
502
503 instance_name = self.op.instance_name
504 # this is just a preventive check, but someone might still add this
505 # instance in the meantime, and creation will fail at lock-add time
506 if instance_name in self.cfg.GetInstanceList():
507 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
508 instance_name, errors.ECODE_EXISTS)
509
510 self.add_locks[locking.LEVEL_INSTANCE] = instance_name
511
512 if self.op.iallocator:
513 # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
514 # specifying a group on instance creation and then selecting nodes from
515 # that group
516 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
517 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
518
519 if self.op.opportunistic_locking:
520 self.opportunistic_locks[locking.LEVEL_NODE] = True
521 self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
522 else:
523 self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
524 nodelist = [self.op.pnode]
525 if self.op.snode is not None:
526 self.op.snode = ExpandNodeName(self.cfg, self.op.snode)
527 nodelist.append(self.op.snode)
528 self.needed_locks[locking.LEVEL_NODE] = nodelist
529
530 # in case of import lock the source node too
531 if self.op.mode == constants.INSTANCE_IMPORT:
532 src_node = self.op.src_node
533 src_path = self.op.src_path
534
535 if src_path is None:
536 self.op.src_path = src_path = self.op.instance_name
537
538 if src_node is None:
539 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
540 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
541 self.op.src_node = None
542 if os.path.isabs(src_path):
543 raise errors.OpPrereqError("Importing an instance from a path"
544 " requires a source node option",
545 errors.ECODE_INVAL)
546 else:
547 self.op.src_node = src_node = ExpandNodeName(self.cfg, src_node)
548 if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
549 self.needed_locks[locking.LEVEL_NODE].append(src_node)
550 if not os.path.isabs(src_path):
551 self.op.src_path = src_path = \
552 utils.PathJoin(pathutils.EXPORT_DIR, src_path)
553
554 self.needed_locks[locking.LEVEL_NODE_RES] = \
555 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
556
557 def _RunAllocator(self):
558 """Run the allocator based on input opcode.
559
560 """
561 if self.op.opportunistic_locking:
562 # Only consider nodes for which a lock is held
563 node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
564 else:
565 node_whitelist = None
566
567 #TODO Export network to iallocator so that it chooses a pnode
568 # in a nodegroup that has the desired network connected to
569 req = _CreateInstanceAllocRequest(self.op, self.disks,
570 self.nics, self.be_full,
571 node_whitelist)
572 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
573
574 ial.Run(self.op.iallocator)
575
576 if not ial.success:
577 # When opportunistic locks are used only a temporary failure is generated
578 if self.op.opportunistic_locking:
579 ecode = errors.ECODE_TEMP_NORES
580 else:
581 ecode = errors.ECODE_NORES
582
583 raise errors.OpPrereqError("Can't compute nodes using"
584 " iallocator '%s': %s" %
585 (self.op.iallocator, ial.info),
586 ecode)
587
588 self.op.pnode = ial.result[0]
589 self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
590 self.op.instance_name, self.op.iallocator,
591 utils.CommaJoin(ial.result))
592
593 assert req.RequiredNodes() in (1, 2), "Wrong node count from iallocator"
594
595 if req.RequiredNodes() == 2:
596 self.op.snode = ial.result[1]
597
598 def BuildHooksEnv(self):
599 """Build hooks env.
600
601 This runs on master, primary and secondary nodes of the instance.
602
603 """
604 env = {
605 "ADD_MODE": self.op.mode,
606 }
607 if self.op.mode == constants.INSTANCE_IMPORT:
608 env["SRC_NODE"] = self.op.src_node
609 env["SRC_PATH"] = self.op.src_path
610 env["SRC_IMAGES"] = self.src_images
611
612 env.update(BuildInstanceHookEnv(
613 name=self.op.instance_name,
614 primary_node=self.op.pnode,
615 secondary_nodes=self.secondaries,
616 status=self.op.start,
617 os_type=self.op.os_type,
618 minmem=self.be_full[constants.BE_MINMEM],
619 maxmem=self.be_full[constants.BE_MAXMEM],
620 vcpus=self.be_full[constants.BE_VCPUS],
621 nics=NICListToTuple(self, self.nics),
622 disk_template=self.op.disk_template,
623 disks=[(d[constants.IDISK_NAME], d.get("uuid", ""),
624 d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
625 for d in self.disks],
626 bep=self.be_full,
627 hvp=self.hv_full,
628 hypervisor_name=self.op.hypervisor,
629 tags=self.op.tags,
630 ))
631
632 return env
633
634 def BuildHooksNodes(self):
635 """Build hooks nodes.
636
637 """
638 nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
639 return nl, nl
640
641 def _ReadExportInfo(self):
642 """Reads the export information from disk.
643
644 It will override the opcode source node and path with the actual
645 information, if these two were not specified before.
646
647 @return: the export information
648
649 """
650 assert self.op.mode == constants.INSTANCE_IMPORT
651
652 src_node = self.op.src_node
653 src_path = self.op.src_path
654
655 if src_node is None:
656 locked_nodes = self.owned_locks(locking.LEVEL_NODE)
657 exp_list = self.rpc.call_export_list(locked_nodes)
658 found = False
659 for node in exp_list:
660 if exp_list[node].fail_msg:
661 continue
662 if src_path in exp_list[node].payload:
663 found = True
664 self.op.src_node = src_node = node
665 self.op.src_path = src_path = utils.PathJoin(pathutils.EXPORT_DIR,
666 src_path)
667 break
668 if not found:
669 raise errors.OpPrereqError("No export found for relative path %s" %
670 src_path, errors.ECODE_INVAL)
671
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)
675
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)
680
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)
686 return export_info
687
688 def _ReadExportParams(self, einfo):
689 """Use export parameters as defaults.
690
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
693 that declares them.
694
695 """
696 self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
697
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,
701 "disk_template")
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:"
705 " %s" %
706 " ".join(constants.DISK_TEMPLATES),
707 errors.ECODE_INVAL)
708 else:
709 raise errors.OpPrereqError("No disk template specified and the export"
710 " is missing the disk_template information",
711 errors.ECODE_INVAL)
712
713 if not self.op.disks:
714 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",
724 errors.ECODE_INVAL)
725
726 if not self.op.nics:
727 nics = []
728 for idx in range(constants.MAX_NICS):
729 if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
730 ndict = {}
731 for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
732 v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
733 ndict[name] = v
734 nics.append(ndict)
735 else:
736 break
737 self.op.nics = nics
738
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()
741
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")
745
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
752
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
764 else:
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)
770
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
776
777 def _RevertToDefaults(self, cluster):
778 """Revert the instance parameters to the default values.
779
780 """
781 # hvparams
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]
786 # beparams
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]
791 # nic params
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]:
796 del nic[name]
797 # osparams
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]
802
803 def _CalculateFileStorageDir(self):
804 """Calculate final instance file storage dir.
805
806 """
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
811 joinargs = []
812
813 if self.op.disk_template == constants.DT_SHARED_FILE:
814 get_fsd_fn = self.cfg.GetSharedFileStorageDir
815 else:
816 get_fsd_fn = self.cfg.GetFileStorageDir
817
818 cfg_storagedir = get_fsd_fn()
819 if not cfg_storagedir:
820 raise errors.OpPrereqError("Cluster file storage dir not defined",
821 errors.ECODE_STATE)
822 joinargs.append(cfg_storagedir)
823
824 if self.op.file_storage_dir is not None:
825 joinargs.append(self.op.file_storage_dir)
826
827 joinargs.append(self.op.instance_name)
828
829 # pylint: disable=W0142
830 self.instance_file_storage_dir = utils.PathJoin(*joinargs)
831
832 def CheckPrereq(self): # pylint: disable=R0914
833 """Check prerequisites.
834
835 """
836 self._CalculateFileStorageDir()
837
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")
842 else:
843 self._old_instance_name = None
844
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)
849
850 if (self.op.hypervisor is None or
851 self.op.hypervisor == constants.VALUE_AUTO):
852 self.op.hypervisor = self.cfg.GetHypervisorType()
853
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"
858 " cluster (%s)" %
859 (self.op.hypervisor, ",".join(enabled_hvs)),
860 errors.ECODE_STATE)
861
862 # Check tag validity
863 for tag in self.op.tags:
864 objects.TaggableObject.ValidateTag(tag)
865
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,
869 self.op.hvparams)
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")
876
877 # fill and remember the beparams dict
878 self.be_full = _ComputeFullBeParams(self.op, cluster)
879
880 # build os parameters
881 self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
882
883 # now that hvp/bep are in final format, let's reset to defaults,
884 # if told to do so
885 if self.op.identify_defaults:
886 self._RevertToDefaults(cluster)
887
888 # NIC buildup
889 self.nics = _ComputeNics(self.op, cluster, self.check_ip, self.cfg,
890 self.proc.GetECId())
891
892 # disk checks/pre-build
893 default_vg = self.cfg.GetVGName()
894 self.disks = ComputeDisks(self.op, default_vg)
895
896 if self.op.mode == constants.INSTANCE_IMPORT:
897 disk_images = []
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)
905 else:
906 disk_images.append(False)
907
908 self.src_images = disk_images
909
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)
915
916 # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
917
918 # ip ping checks (we use the same ip that was resolved in ExpandNames)
919 if self.op.ip_check:
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)
924
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())
936
937 #### allocator run
938
939 if self.op.iallocator is not None:
940 self._RunAllocator()
941
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)
947
948 assert (self.owned_locks(locking.LEVEL_NODE) ==
949 self.owned_locks(locking.LEVEL_NODE_RES)), \
950 "Node locks differ from node resource locks"
951
952 #### node related checks
953
954 # check primary node
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
958 if pnode.offline:
959 raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
960 pnode.name, errors.ECODE_STATE)
961 if pnode.drained:
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)
967
968 self.secondaries = []
969
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),
982 errors.ECODE_INVAL)
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:
988 try:
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,
993 errors.ECODE_STATE)
994 self.LogInfo("Chose IP %s from network %s", nic.ip, nobj.name)
995 else:
996 try:
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)
1003
1004 # net is None, ip None or given
1005 elif self.op.conflicts_check:
1006 _CheckForConflictingIp(self, nic.ip, self.pnode.name)
1007
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)
1017
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"
1023 " used")
1024
1025 if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
1026 nodes = [pnode]
1027 if self.op.disk_template in constants.DTS_INT_MIRROR:
1028 nodes.append(snode)
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,
1033 errors.ECODE_STATE)
1034
1035 nodenames = [pnode.name] + self.secondaries
1036
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
1045 pass
1046 else:
1047 # Check lv size requirements, if not adopting
1048 req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
1049 CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
1050
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",
1057 errors.ECODE_INVAL)
1058 for lv_name in all_lvs:
1059 try:
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)
1066
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)
1069
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
1074
1075 delta = all_lvs.difference(node_lvs.keys())
1076 if delta:
1077 raise errors.OpPrereqError("Missing logical volume(s): %s" %
1078 utils.CommaJoin(delta),
1079 errors.ECODE_INVAL)
1080 online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
1081 if online_lvs:
1082 raise errors.OpPrereqError("Online logical volumes found, cannot"
1083 " adopt: %s" % utils.CommaJoin(online_lvs),
1084 errors.ECODE_STATE)
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]))
1090
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",
1097 errors.ECODE_INVAL)
1098 baddisks = [d for d in all_disks
1099 if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
1100 if baddisks:
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),
1105 errors.ECODE_INVAL)
1106
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" %
1110 pnode.name)
1111 node_disks = node_disks.payload
1112 delta = all_disks.difference(node_disks.keys())
1113 if delta:
1114 raise errors.OpPrereqError("Missing block device(s): %s" %
1115 utils.CommaJoin(delta),
1116 errors.ECODE_INVAL)
1117 for dsk in self.disks:
1118 dsk[constants.IDISK_SIZE] = \
1119 int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
1120
1121 # Verify instance specs
1122 spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
1123 ispec = {
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,
1131 }
1132
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)
1141
1142 CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
1143
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)
1147
1148 CheckNicsBridgesExist(self, self.nics, self.pnode.name)
1149
1150 #TODO: _CheckExtParams (remotely)
1151 # Check parameters for extstorage
1152
1153 # memory check on primary node
1154 #TODO(dynmem): use MINMEM for checking
1155 if self.op.start:
1156 CheckNodeFreeMemory(self, self.pnode.name,
1157 "creating instance %s" % self.op.instance_name,
1158 self.be_full[constants.BE_MAXMEM],
1159 self.op.hypervisor)
1160
1161 self.dry_run_result = list(nodenames)
1162
1163 def Exec(self, feedback_fn):
1164 """Create and add the instance to the cluster.
1165
1166 """
1167 instance = self.op.instance_name
1168 pnode_name = self.pnode.name
1169
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)
1174
1175 ht_kind = self.op.hypervisor
1176 if ht_kind in constants.HTS_REQ_PORT:
1177 network_port = self.cfg.AllocatePort()
1178 else:
1179 network_port = None
1180
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,
1189 self.secondaries,
1190 self.disks,
1191 self.instance_file_storage_dir,
1192 self.op.file_driver,
1193 0,
1194 feedback_fn,
1195 self.cfg.GetGroupDiskParams(nodegroup))
1196
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 disks_active=False,
1202 admin_state=constants.ADMINST_DOWN,
1203 network_port=network_port,
1204 beparams=self.op.beparams,
1205 hvparams=self.op.hvparams,
1206 hypervisor=self.op.hypervisor,
1207 osparams=self.op.osparams,
1208 )
1209
1210 if self.op.tags:
1211 for tag in self.op.tags:
1212 iobj.AddTag(tag)
1213
1214 if self.adopt_disks:
1215 if self.op.disk_template == constants.DT_PLAIN:
1216 # rename LVs to the newly-generated names; we need to construct
1217 # 'fake' LV disks with the old data, plus the new unique_id
1218 tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
1219 rename_to = []
1220 for t_dsk, a_dsk in zip(tmp_disks, self.disks):
1221 rename_to.append(t_dsk.logical_id)
1222 t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
1223 self.cfg.SetDiskID(t_dsk, pnode_name)
1224 result = self.rpc.call_blockdev_rename(pnode_name,
1225 zip(tmp_disks, rename_to))
1226 result.Raise("Failed to rename adoped LVs")
1227 else:
1228 feedback_fn("* creating instance disks...")
1229 try:
1230 CreateDisks(self, iobj)
1231 except errors.OpExecError:
1232 self.LogWarning("Device creation failed")
1233 self.cfg.ReleaseDRBDMinors(instance)
1234 raise
1235
1236 feedback_fn("adding instance %s to cluster config" % instance)
1237
1238 self.cfg.AddInstance(iobj, self.proc.GetECId())
1239
1240 # Declare that we don't want to remove the instance lock anymore, as we've
1241 # added the instance to the config
1242 del self.remove_locks[locking.LEVEL_INSTANCE]
1243
1244 if self.op.mode == constants.INSTANCE_IMPORT:
1245 # Release unused nodes
1246 ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
1247 else:
1248 # Release all nodes
1249 ReleaseLocks(self, locking.LEVEL_NODE)
1250
1251 disk_abort = False
1252 if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
1253 feedback_fn("* wiping instance disks...")
1254 try:
1255 WipeDisks(self, iobj)
1256 except errors.OpExecError, err:
1257 logging.exception("Wiping disks failed")
1258 self.LogWarning("Wiping instance disks failed (%s)", err)
1259 disk_abort = True
1260
1261 if disk_abort:
1262 # Something is already wrong with the disks, don't do anything else
1263 pass
1264 elif self.op.wait_for_sync:
1265 disk_abort = not WaitForSync(self, iobj)
1266 elif iobj.disk_template in constants.DTS_INT_MIRROR:
1267 # make sure the disks are not degraded (still sync-ing is ok)
1268 feedback_fn("* checking mirrors status")
1269 disk_abort = not WaitForSync(self, iobj, oneshot=True)
1270 else:
1271 disk_abort = False
1272
1273 if disk_abort:
1274 RemoveDisks(self, iobj)
1275 self.cfg.RemoveInstance(iobj.name)
1276 # Make sure the instance lock gets removed
1277 self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
1278 raise errors.OpExecError("There are some degraded disks for"
1279 " this instance")
1280
1281 # instance disks are now active
1282 iobj.disks_active = True
1283
1284 # Release all node resource locks
1285 ReleaseLocks(self, locking.LEVEL_NODE_RES)
1286
1287 if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
1288 # we need to set the disks ID to the primary node, since the
1289 # preceding code might or might have not done it, depending on
1290 # disk template and other options
1291 for disk in iobj.disks:
1292 self.cfg.SetDiskID(disk, pnode_name)
1293 if self.op.mode == constants.INSTANCE_CREATE:
1294 if not self.op.no_install:
1295 pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
1296 not self.op.wait_for_sync)
1297 if pause_sync:
1298 feedback_fn("* pausing disk sync to install instance OS")
1299 result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1300 (iobj.disks,
1301 iobj), True)
1302 for idx, success in enumerate(result.payload):
1303 if not success:
1304 logging.warn("pause-sync of instance %s for disk %d failed",
1305 instance, idx)
1306
1307 feedback_fn("* running the instance OS create scripts...")
1308 # FIXME: pass debug option from opcode to backend
1309 os_add_result = \
1310 self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
1311 self.op.debug_level)
1312 if pause_sync:
1313 feedback_fn("* resuming disk sync")
1314 result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1315 (iobj.disks,
1316 iobj), False)
1317 for idx, success in enumerate(result.payload):
1318 if not success:
1319 logging.warn("resume-sync of instance %s for disk %d failed",
1320 instance, idx)
1321
1322 os_add_result.Raise("Could not add os for instance %s"
1323 " on node %s" % (instance, pnode_name))
1324
1325 else:
1326 if self.op.mode == constants.INSTANCE_IMPORT:
1327 feedback_fn("* running the instance OS import scripts...")
1328
1329 transfers = []
1330
1331 for idx, image in enumerate(self.src_images):
1332 if not image:
1333 continue
1334
1335 # FIXME: pass debug option from opcode to backend
1336 dt = masterd.instance.DiskTransfer("disk/%s" % idx,
1337 constants.IEIO_FILE, (image, ),
1338 constants.IEIO_SCRIPT,
1339 (iobj.disks[idx], idx),
1340 None)
1341 transfers.append(dt)
1342
1343 import_result = \
1344 masterd.instance.TransferInstanceData(self, feedback_fn,
1345 self.op.src_node, pnode_name,
1346 self.pnode.secondary_ip,
1347 iobj, transfers)
1348 if not compat.all(import_result):
1349 self.LogWarning("Some disks for instance %s on node %s were not"
1350 " imported successfully" % (instance, pnode_name))
1351
1352 rename_from = self._old_instance_name
1353
1354 elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
1355 feedback_fn("* preparing remote import...")
1356 # The source cluster will stop the instance before attempting to make
1357 # a connection. In some cases stopping an instance can take a long
1358 # time, hence the shutdown timeout is added to the connection
1359 # timeout.
1360 connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
1361 self.op.source_shutdown_timeout)
1362 timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
1363
1364 assert iobj.primary_node == self.pnode.name
1365 disk_results = \
1366 masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
1367 self.source_x509_ca,
1368 self._cds, timeouts)
1369 if not compat.all(disk_results):
1370 # TODO: Should the instance still be started, even if some disks
1371 # failed to import (valid for local imports, too)?
1372 self.LogWarning("Some disks for instance %s on node %s were not"
1373 " imported successfully" % (instance, pnode_name))
1374
1375 rename_from = self.source_instance_name
1376
1377 else:
1378 # also checked in the prereq part
1379 raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
1380 % self.op.mode)
1381
1382 # Run rename script on newly imported instance
1383 assert iobj.name == instance
1384 feedback_fn("Running rename script for %s" % instance)
1385 result = self.rpc.call_instance_run_rename(pnode_name, iobj,
1386 rename_from,
1387 self.op.debug_level)
1388 if result.fail_msg:
1389 self.LogWarning("Failed to run rename script for %s on node"
1390 " %s: %s" % (instance, pnode_name, result.fail_msg))
1391
1392 assert not self.owned_locks(locking.LEVEL_NODE_RES)
1393
1394 if self.op.start:
1395 iobj.admin_state = constants.ADMINST_UP
1396 self.cfg.Update(iobj, feedback_fn)
1397 logging.info("Starting instance %s on node %s", instance, pnode_name)
1398 feedback_fn("* starting instance...")
1399 result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
1400 False, self.op.reason)
1401 result.Raise("Could not start instance")
1402
1403 return list(iobj.all_nodes)
1404
1405
1406 class LUInstanceRename(LogicalUnit):
1407 """Rename an instance.
1408
1409 """
1410 HPATH = "instance-rename"
1411 HTYPE = constants.HTYPE_INSTANCE
1412
1413 def CheckArguments(self):
1414 """Check arguments.
1415
1416 """
1417 if self.op.ip_check and not self.op.name_check:
1418 # TODO: make the ip check more flexible and not depend on the name check
1419 raise errors.OpPrereqError("IP address check requires a name check",
1420 errors.ECODE_INVAL)
1421
1422 def BuildHooksEnv(self):
1423 """Build hooks env.
1424
1425 This runs on master, primary and secondary nodes of the instance.
1426
1427 """
1428 env = BuildInstanceHookEnvByObject(self, self.instance)
1429 env["INSTANCE_NEW_NAME"] = self.op.new_name
1430 return env
1431
1432 def BuildHooksNodes(self):
1433 """Build hooks nodes.
1434
1435 """
1436 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
1437 return (nl, nl)
1438
1439 def CheckPrereq(self):
1440 """Check prerequisites.
1441
1442 This checks that the instance is in the cluster and is not running.
1443
1444 """
1445 self.op.instance_name = ExpandInstanceName(self.cfg,
1446 self.op.instance_name)
1447 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1448 assert instance is not None
1449 CheckNodeOnline(self, instance.primary_node)
1450 CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
1451 msg="cannot rename")
1452 self.instance = instance
1453
1454 new_name = self.op.new_name
1455 if self.op.name_check:
1456 hostname = _CheckHostnameSane(self, new_name)
1457 new_name = self.op.new_name = hostname.name
1458 if (self.op.ip_check and
1459 netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
1460 raise errors.OpPrereqError("IP %s of instance %s already in use" %
1461 (hostname.ip, new_name),
1462 errors.ECODE_NOTUNIQUE)
1463
1464 instance_list = self.cfg.GetInstanceList()
1465 if new_name in instance_list and new_name != instance.name:
1466 raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
1467 new_name, errors.ECODE_EXISTS)
1468
1469 def Exec(self, feedback_fn):
1470 """Rename the instance.
1471
1472 """
1473 inst = self.instance
1474 old_name = inst.name
1475
1476 rename_file_storage = False
1477 if (inst.disk_template in constants.DTS_FILEBASED and
1478 self.op.new_name != inst.name):
1479 old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1480 rename_file_storage = True
1481
1482 self.cfg.RenameInstance(inst.name, self.op.new_name)
1483 # Change the instance lock. This is definitely safe while we hold the BGL.
1484 # Otherwise the new lock would have to be added in acquired mode.
1485 assert self.REQ_BGL
1486 assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER)
1487 self.glm.remove(locking.LEVEL_INSTANCE, old_name)
1488 self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
1489
1490 # re-read the instance from the configuration after rename
1491 inst = self.cfg.GetInstanceInfo(self.op.new_name)
1492
1493 if rename_file_storage:
1494 new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1495 result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
1496 old_file_storage_dir,
1497 new_file_storage_dir)
1498 result.Raise("Could not rename on node %s directory '%s' to '%s'"
1499 " (but the instance has been renamed in Ganeti)" %
1500 (inst.primary_node, old_file_storage_dir,
1501 new_file_storage_dir))
1502
1503 StartInstanceDisks(self, inst, None)
1504 # update info on disks
1505 info = GetInstanceInfoText(inst)
1506 for (idx, disk) in enumerate(inst.disks):
1507 for node in inst.all_nodes:
1508 self.cfg.SetDiskID(disk, node)
1509 result = self.rpc.call_blockdev_setinfo(node, disk, info)
1510 if result.fail_msg:
1511 self.LogWarning("Error setting info on node %s for disk %s: %s",
1512 node, idx, result.fail_msg)
1513 try:
1514 result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
1515 old_name, self.op.debug_level)
1516 msg = result.fail_msg
1517 if msg:
1518 msg = ("Could not run OS rename script for instance %s on node %s"
1519 " (but the instance has been renamed in Ganeti): %s" %
1520 (inst.name, inst.primary_node, msg))
1521 self.LogWarning(msg)
1522 finally:
1523 ShutdownInstanceDisks(self, inst)
1524
1525 return inst.name
1526
1527
1528 class LUInstanceRemove(LogicalUnit):
1529 """Remove an instance.
1530
1531 """
1532 HPATH = "instance-remove"
1533 HTYPE = constants.HTYPE_INSTANCE
1534 REQ_BGL = False
1535
1536 def ExpandNames(self):
1537 self._ExpandAndLockInstance()
1538 self.needed_locks[locking.LEVEL_NODE] = []
1539 self.needed_locks[locking.LEVEL_NODE_RES] = []
1540 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
1541
1542 def DeclareLocks(self, level):
1543 if level == locking.LEVEL_NODE:
1544 self._LockInstancesNodes()
1545 elif level == locking.LEVEL_NODE_RES:
1546 # Copy node locks
1547 self.needed_locks[locking.LEVEL_NODE_RES] = \
1548 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1549
1550 def BuildHooksEnv(self):
1551 """Build hooks env.
1552
1553 This runs on master, primary and secondary nodes of the instance.
1554
1555 """
1556 env = BuildInstanceHookEnvByObject(self, self.instance)
1557 env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
1558 return env
1559
1560 def BuildHooksNodes(self):
1561 """Build hooks nodes.
1562
1563 """
1564 nl = [self.cfg.GetMasterNode()]
1565 nl_post = list(self.instance.all_nodes) + nl
1566 return (nl, nl_post)
1567
1568 def CheckPrereq(self):
1569 """Check prerequisites.
1570
1571 This checks that the instance is in the cluster.
1572
1573 """
1574 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1575 assert self.instance is not None, \
1576 "Cannot retrieve locked instance %s" % self.op.instance_name
1577
1578 def Exec(self, feedback_fn):
1579 """Remove the instance.
1580
1581 """
1582 instance = self.instance
1583 logging.info("Shutting down instance %s on node %s",
1584 instance.name, instance.primary_node)
1585
1586 result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
1587 self.op.shutdown_timeout,
1588 self.op.reason)
1589 msg = result.fail_msg
1590 if msg:
1591 if self.op.ignore_failures:
1592 feedback_fn("Warning: can't shutdown instance: %s" % msg)
1593 else:
1594 raise errors.OpExecError("Could not shutdown instance %s on"
1595 " node %s: %s" %
1596 (instance.name, instance.primary_node, msg))
1597
1598 assert (self.owned_locks(locking.LEVEL_NODE) ==
1599 self.owned_locks(locking.LEVEL_NODE_RES))
1600 assert not (set(instance.all_nodes) -
1601 self.owned_locks(locking.LEVEL_NODE)), \
1602 "Not owning correct locks"
1603
1604 RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
1605
1606
1607 class LUInstanceMove(LogicalUnit):
1608 """Move an instance by data-copying.
1609
1610 """
1611 HPATH = "instance-move"
1612 HTYPE = constants.HTYPE_INSTANCE
1613 REQ_BGL = False
1614
1615 def ExpandNames(self):
1616 self._ExpandAndLockInstance()
1617 target_node = ExpandNodeName(self.cfg, self.op.target_node)
1618 self.op.target_node = target_node
1619 self.needed_locks[locking.LEVEL_NODE] = [target_node]
1620 self.needed_locks[locking.LEVEL_NODE_RES] = []
1621 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
1622
1623 def DeclareLocks(self, level):
1624 if level == locking.LEVEL_NODE:
1625 self._LockInstancesNodes(primary_only=True)
1626 elif level == locking.LEVEL_NODE_RES:
1627 # Copy node locks
1628 self.needed_locks[locking.LEVEL_NODE_RES] = \
1629 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1630
1631 def BuildHooksEnv(self):
1632 """Build hooks env.
1633
1634 This runs on master, primary and secondary nodes of the instance.
1635
1636 """
1637 env = {
1638 "TARGET_NODE": self.op.target_node,
1639 "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
1640 }
1641 env.update(BuildInstanceHookEnvByObject(self, self.instance))
1642 return env
1643
1644 def BuildHooksNodes(self):
1645 """Build hooks nodes.
1646
1647 """
1648 nl = [
1649 self.cfg.GetMasterNode(),
1650 self.instance.primary_node,
1651 self.op.target_node,
1652 ]
1653 return (nl, nl)
1654
1655 def CheckPrereq(self):
1656 """Check prerequisites.
1657
1658 This checks that the instance is in the cluster.
1659
1660 """
1661 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1662 assert self.instance is not None, \
1663 "Cannot retrieve locked instance %s" % self.op.instance_name
1664
1665 if instance.disk_template not in constants.DTS_COPYABLE:
1666 raise errors.OpPrereqError("Disk template %s not suitable for copying" %
1667 instance.disk_template, errors.ECODE_STATE)
1668
1669 node = self.cfg.GetNodeInfo(self.op.target_node)
1670 assert node is not None, \
1671 "Cannot retrieve locked node %s" % self.op.target_node
1672
1673 self.target_node = target_node = node.name
1674
1675 if target_node == instance.primary_node:
1676 raise errors.OpPrereqError("Instance %s is already on the node %s" %
1677 (instance.name, target_node),
1678 errors.ECODE_STATE)
1679
1680 bep = self.cfg.GetClusterInfo().FillBE(instance)
1681
1682 for idx, dsk in enumerate(instance.disks):
1683 if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
1684 raise errors.OpPrereqError("Instance disk %d has a complex layout,"
1685 " cannot copy" % idx, errors.ECODE_STATE)
1686
1687 CheckNodeOnline(self, target_node)
1688 CheckNodeNotDrained(self, target_node)
1689 CheckNodeVmCapable(self, target_node)
1690 cluster = self.cfg.GetClusterInfo()
1691 group_info = self.cfg.GetNodeGroup(node.group)
1692 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1693 CheckTargetNodeIPolicy(self, ipolicy, instance, node, self.cfg,
1694 ignore=self.op.ignore_ipolicy)
1695
1696 if instance.admin_state == constants.ADMINST_UP:
1697 # check memory requirements on the secondary node
1698 CheckNodeFreeMemory(self, target_node,
1699 "failing over instance %s" %
1700 instance.name, bep[constants.BE_MAXMEM],
1701 instance.hypervisor)
1702 else:
1703 self.LogInfo("Not checking memory on the secondary node as"
1704 " instance will not be started")
1705
1706 # check bridge existance
1707 CheckInstanceBridgesExist(self, instance, node=target_node)
1708
1709 def Exec(self, feedback_fn):
1710 """Move an instance.
1711
1712 The move is done by shutting it down on its present node, copying
1713 the data over (slow) and starting it on the new node.
1714
1715 """
1716 instance = self.instance
1717
1718 source_node = instance.primary_node
1719 target_node = self.target_node
1720
1721 self.LogInfo("Shutting down instance %s on source node %s",
1722 instance.name, source_node)
1723
1724 assert (self.owned_locks(locking.LEVEL_NODE) ==
1725 self.owned_locks(locking.LEVEL_NODE_RES))
1726
1727 result = self.rpc.call_instance_shutdown(source_node, instance,
1728 self.op.shutdown_timeout,
1729 self.op.reason)
1730 msg = result.fail_msg
1731 if msg:
1732 if self.op.ignore_consistency:
1733 self.LogWarning("Could not shutdown instance %s on node %s."
1734 " Proceeding anyway. Please make sure node"
1735 " %s is down. Error details: %s",
1736 instance.name, source_node, source_node, msg)
1737 else:
1738 raise errors.OpExecError("Could not shutdown instance %s on"
1739 " node %s: %s" %
1740 (instance.name, source_node, msg))
1741
1742 # create the target disks
1743 try:
1744 CreateDisks(self, instance, target_node=target_node)
1745 except errors.OpExecError:
1746 self.LogWarning("Device creation failed")
1747 self.cfg.ReleaseDRBDMinors(instance.name)
1748 raise
1749
1750 cluster_name = self.cfg.GetClusterInfo().cluster_name
1751
1752 errs = []
1753 # activate, get path, copy the data over
1754 for idx, disk in enumerate(instance.disks):
1755 self.LogInfo("Copying data for disk %d", idx)
1756 result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
1757 instance.name, True, idx)
1758 if result.fail_msg:
1759 self.LogWarning("Can't assemble newly created disk %d: %s",
1760 idx, result.fail_msg)
1761 errs.append(result.fail_msg)
1762 break
1763 dev_path = result.payload
1764 result = self.rpc.call_blockdev_export(source_node, (disk, instance),
1765 target_node, dev_path,
1766 cluster_name)
1767 if result.fail_msg:
1768 self.LogWarning("Can't copy data over for disk %d: %s",
1769 idx, result.fail_msg)
1770 errs.append(result.fail_msg)
1771 break
1772
1773 if errs:
1774 self.LogWarning("Some disks failed to copy, aborting")
1775 try:
1776 RemoveDisks(self, instance, target_node=target_node)
1777 finally:
1778 self.cfg.ReleaseDRBDMinors(instance.name)
1779 raise errors.OpExecError("Errors during disk copy: %s" %
1780 (",".join(errs),))
1781
1782 instance.primary_node = target_node
1783 self.cfg.Update(instance, feedback_fn)
1784
1785 self.LogInfo("Removing the disks on the original node")
1786 RemoveDisks(self, instance, target_node=source_node)
1787
1788 # Only start the instance if it's marked as up
1789 if instance.admin_state == constants.ADMINST_UP:
1790 self.LogInfo("Starting instance %s on node %s",
1791 instance.name, target_node)
1792
1793 disks_ok, _ = AssembleInstanceDisks(self, instance,
1794 ignore_secondaries=True)
1795 if not disks_ok:
1796 ShutdownInstanceDisks(self, instance)
1797 raise errors.OpExecError("Can't activate the instance's disks")
1798
1799 result = self.rpc.call_instance_start(target_node,
1800 (instance, None, None), False,
1801 self.op.reason)
1802 msg = result.fail_msg
1803 if msg:
1804 ShutdownInstanceDisks(self, instance)
1805 raise errors.OpExecError("Could not start instance %s on node %s: %s" %
1806 (instance.name, target_node, msg))
1807
1808
1809 class LUInstanceMultiAlloc(NoHooksLU):
1810 """Allocates multiple instances at the same time.
1811
1812 """
1813 REQ_BGL = False
1814
1815 def CheckArguments(self):
1816 """Check arguments.
1817
1818 """
1819 nodes = []
1820 for inst in self.op.instances:
1821 if inst.iallocator is not None:
1822 raise errors.OpPrereqError("iallocator are not allowed to be set on"
1823 " instance objects", errors.ECODE_INVAL)
1824 nodes.append(bool(inst.pnode))
1825 if inst.disk_template in constants.DTS_INT_MIRROR:
1826 nodes.append(bool(inst.snode))
1827
1828 has_nodes = compat.any(nodes)
1829 if compat.all(nodes) ^ has_nodes:
1830 raise errors.OpPrereqError("There are instance objects providing"
1831 " pnode/snode while others do not",
1832 errors.ECODE_INVAL)
1833
1834 if not has_nodes and self.op.iallocator is None:
1835 default_iallocator = self.cfg.GetDefaultIAllocator()
1836 if default_iallocator:
1837 self.op.iallocator = default_iallocator
1838 else:
1839 raise errors.OpPrereqError("No iallocator or nodes on the instances"
1840 " given and no cluster-wide default"
1841 " iallocator found; please specify either"
1842 " an iallocator or nodes on the instances"
1843 " or set a cluster-wide default iallocator",
1844 errors.ECODE_INVAL)
1845
1846 _CheckOpportunisticLocking(self.op)
1847
1848 dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
1849 if dups:
1850 raise errors.OpPrereqError("There are duplicate instance names: %s" %
1851 utils.CommaJoin(dups), errors.ECODE_INVAL)
1852
1853 def ExpandNames(self):
1854 """Calculate the locks.
1855
1856 """
1857 self.share_locks = ShareAll()
1858 self.needed_locks = {
1859 # iallocator will select nodes and even if no iallocator is used,
1860 # collisions with LUInstanceCreate should be avoided
1861 locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
1862 }
1863
1864 if self.op.iallocator:
1865 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
1866 self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
1867
1868 if self.op.opportunistic_locking:
1869 self.opportunistic_locks[locking.LEVEL_NODE] = True
1870 self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
1871 else:
1872 nodeslist = []
1873 for inst in self.op.instances:
1874 inst.pnode = ExpandNodeName(self.cfg, inst.pnode)
1875 nodeslist.append(inst.pnode)
1876 if inst.snode is not None:
1877 inst.snode = ExpandNodeName(self.cfg, inst.snode)
1878 nodeslist.append(inst.snode)
1879
1880 self.needed_locks[locking.LEVEL_NODE] = nodeslist
1881 # Lock resources of instance's primary and secondary nodes (copy to
1882 # prevent accidential modification)
1883 self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
1884
1885 def CheckPrereq(self):
1886 """Check prerequisite.
1887
1888 """
1889 if self.op.iallocator:
1890 cluster = self.cfg.GetClusterInfo()
1891 default_vg = self.cfg.GetVGName()
1892 ec_id = self.proc.GetECId()
1893
1894 if self.op.opportunistic_locking:
1895 # Only consider nodes for which a lock is held
1896 node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
1897 else:
1898 node_whitelist = None
1899
1900 insts = [_CreateInstanceAllocRequest(op, ComputeDisks(op, default_vg),
1901 _ComputeNics(op, cluster, None,
1902 self.cfg, ec_id),
1903 _ComputeFullBeParams(op, cluster),
1904 node_whitelist)
1905 for op in self.op.instances]
1906
1907 req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
1908 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
1909
1910 ial.Run(self.op.iallocator)
1911
1912 if not ial.success:
1913 raise errors.OpPrereqError("Can't compute nodes using"
1914 " iallocator '%s': %s" %
1915 (self.op.iallocator, ial.info),
1916 errors.ECODE_NORES)
1917
1918 self.ia_result = ial.result
1919
1920 if self.op.dry_run:
1921 self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
1922 constants.JOB_IDS_KEY: [],
1923 })
1924
1925 def _ConstructPartialResult(self):
1926 """Contructs the partial result.
1927
1928 """
1929 if self.op.iallocator:
1930 (allocatable, failed_insts) = self.ia_result
1931 allocatable_insts = map(compat.fst, allocatable)
1932 else:
1933 allocatable_insts = [op.instance_name for op in self.op.instances]
1934 failed_insts = []
1935
1936 return {
1937 opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY: allocatable_insts,
1938 opcodes.OpInstanceMultiAlloc.FAILED_KEY: failed_insts,
1939 }
1940
1941 def Exec(self, feedback_fn):
1942 """Executes the opcode.
1943
1944 """
1945 jobs = []
1946 if self.op.iallocator:
1947 op2inst = dict((op.instance_name, op) for op in self.op.instances)
1948 (allocatable, failed) = self.ia_result
1949
1950 for (name, nodes) in allocatable:
1951 op = op2inst.pop(name)
1952
1953 if len(nodes) > 1:
1954 (op.pnode, op.snode) = nodes
1955 else:
1956 (op.pnode,) = nodes
1957
1958 jobs.append([op])
1959
1960 missing = set(op2inst.keys()) - set(failed)
1961 assert not missing, \
1962 "Iallocator did return incomplete result: %s" % \
1963 utils.CommaJoin(missing)
1964 else:
1965 jobs.extend([op] for op in self.op.instances)
1966
1967 return ResultWithJobs(jobs, **self._ConstructPartialResult())
1968
1969
1970 class _InstNicModPrivate:
1971 """Data structure for network interface modifications.
1972
1973 Used by L{LUInstanceSetParams}.
1974
1975 """
1976 def __init__(self):
1977 self.params = None
1978 self.filled = None
1979
1980
1981 def _PrepareContainerMods(mods, private_fn):
1982 """Prepares a list of container modifications by adding a private data field.
1983
1984 @type mods: list of tuples; (operation, index, parameters)
1985 @param mods: List of modifications
1986 @type private_fn: callable or None
1987 @param private_fn: Callable for constructing a private data field for a
1988 modification
1989 @rtype: list
1990
1991 """
1992 if private_fn is None:
1993 fn = lambda: None
1994 else:
1995 fn = private_fn
1996
1997 return [(op, idx, params, fn()) for (op, idx, params) in mods]
1998
1999
2000 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
2001 """Checks if nodes have enough physical CPUs
2002
2003 This function checks if all given nodes have the needed number of
2004 physical CPUs. In case any node has less CPUs or we cannot get the
2005 information from the node, this function raises an OpPrereqError
2006 exception.
2007
2008 @type lu: C{LogicalUnit}
2009 @param lu: a logical unit from which we get configuration data
2010 @type nodenames: C{list}
2011 @param nodenames: the list of node names to check
2012 @type requested: C{int}
2013 @param requested: the minimum acceptable number of physical CPUs
2014 @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
2015 or we cannot check the node
2016
2017 """
2018 nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name], None)
2019 for node in nodenames:
2020 info = nodeinfo[node]
2021 info.Raise("Cannot get current information from node %s" % node,
2022 prereq=True, ecode=errors.ECODE_ENVIRON)
2023 (_, _, (hv_info, )) = info.payload
2024 num_cpus = hv_info.get("cpu_total", None)
2025 if not isinstance(num_cpus, int):
2026 raise errors.OpPrereqError("Can't compute the number of physical CPUs"
2027 " on node %s, result was '%s'" %
2028 (node, num_cpus), errors.ECODE_ENVIRON)
2029 if requested > num_cpus:
2030 raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
2031 "required" % (node, num_cpus, requested),
2032 errors.ECODE_NORES)
2033
2034
2035 def GetItemFromContainer(identifier, kind, container):
2036 """Return the item refered by the identifier.
2037
2038 @type identifier: string
2039 @param identifier: Item index or name or UUID
2040 @type kind: string
2041 @param kind: One-word item description
2042 @type container: list
2043 @param container: Container to get the item from
2044
2045 """
2046 # Index
2047 try:
2048 idx = int(identifier)
2049 if idx == -1:
2050 # Append
2051 absidx = len(container) - 1
2052 elif idx < 0:
2053 raise IndexError("Not accepting negative indices other than -1")
2054 elif idx > len(container):
2055 raise IndexError("Got %s index %s, but there are only %s" %
2056 (kind, idx, len(container)))
2057 else:
2058 absidx = idx
2059 return (absidx, container[idx])
2060 except ValueError:
2061 pass
2062
2063 for idx, item in enumerate(container):
2064 if item.uuid == identifier or item.name == identifier:
2065 return (idx, item)
2066
2067 raise errors.OpPrereqError("Cannot find %s with identifier %s" %
2068 (kind, identifier), errors.ECODE_NOENT)
2069
2070
2071 def _ApplyContainerMods(kind, container, chgdesc, mods,
2072 create_fn, modify_fn, remove_fn):
2073 """Applies descriptions in C{mods} to C{container}.
2074
2075 @type kind: string
2076 @param kind: One-word item description
2077 @type container: list
2078 @param container: Container to modify
2079 @type chgdesc: None or list
2080 @param chgdesc: List of applied changes
2081 @type mods: list
2082 @param mods: Modifications as returned by L{_PrepareContainerMods}
2083 @type create_fn: callable
2084 @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
2085 receives absolute item index, parameters and private data object as added
2086 by L{_PrepareContainerMods}, returns tuple containing new item and changes
2087 as list
2088 @type modify_fn: callable
2089 @param modify_fn: Callback for modifying an existing item
2090 (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
2091 and private data object as added by L{_PrepareContainerMods}, returns
2092 changes as list
2093 @type remove_fn: callable
2094 @param remove_fn: Callback on removing item; receives absolute item index,
2095 item and private data object as added by L{_PrepareContainerMods}
2096
2097 """
2098 for (op, identifier, params, private) in mods:
2099 changes = None
2100
2101 if op == constants.DDM_ADD:
2102 # Calculate where item will be added
2103 # When adding an item, identifier can only be an index
2104 try:
2105 idx = int(identifier)
2106 except ValueError:
2107 raise errors.OpPrereqError("Only possitive integer or -1 is accepted as"
2108 " identifier for %s" % constants.DDM_ADD,
2109 errors.ECODE_INVAL)
2110 if idx == -1:
2111 addidx = len(container)
2112 else:
2113 if idx < 0:
2114 raise IndexError("Not accepting negative indices other than -1")
2115 elif idx > len(container):
2116 raise IndexError("Got %s index %s, but there are only %s" %
2117 (kind, idx, len(container)))
2118 addidx = idx
2119
2120 if create_fn is None:
2121 item = params
2122 else:
2123 (item, changes) = create_fn(addidx, params, private)
2124
2125 if idx == -1:
2126 container.append(item)
2127 else:
2128 assert idx >= 0
2129 assert idx <= len(container)
2130 # list.insert does so before the specified index
2131 container.insert(idx, item)
2132 else:
2133 # Retrieve existing item
2134 (absidx, item) = GetItemFromContainer(identifier, kind, container)
2135
2136 if op == constants.DDM_REMOVE:
2137 assert not params
2138
2139 if remove_fn is not None:
2140 remove_fn(absidx, item, private)
2141
2142 changes = [("%s/%s" % (kind, absidx), "remove")]
2143
2144 assert container[absidx] == item
2145 del container[absidx]
2146 elif op == constants.DDM_MODIFY:
2147 if modify_fn is not None:
2148 changes = modify_fn(absidx, item, params, private)
2149 else:
2150 raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2151
2152 assert _TApplyContModsCbChanges(changes)
2153
2154 if not (chgdesc is None or changes is None):
2155 chgdesc.extend(changes)
2156
2157
2158 def _UpdateIvNames(base_index, disks):
2159 """Updates the C{iv_name} attribute of disks.
2160
2161 @type disks: list of L{objects.Disk}
2162
2163 """
2164 for (idx, disk) in enumerate(disks):
2165 disk.iv_name = "disk/%s" % (base_index + idx, )
2166
2167
2168 class LUInstanceSetParams(LogicalUnit):
2169 """Modifies an instances's parameters.
2170
2171 """
2172 HPATH = "instance-modify"
2173 HTYPE = constants.HTYPE_INSTANCE
2174 REQ_BGL = False
2175
2176 @staticmethod
2177 def _UpgradeDiskNicMods(kind, mods, verify_fn):
2178 assert ht.TList(mods)
2179 assert not mods or len(mods[0]) in (2, 3)
2180
2181 if mods and len(mods[0]) == 2:
2182 result = []
2183
2184 addremove = 0
2185 for op, params in mods:
2186 if op in (constants.DDM_ADD, constants.DDM_REMOVE):
2187 result.append((op, -1, params))
2188 addremove += 1
2189
2190 if addremove > 1:
2191 raise errors.OpPrereqError("Only one %s add or remove operation is"
2192 " supported at a time" % kind,
2193 errors.ECODE_INVAL)
2194 else:
2195 result.append((constants.DDM_MODIFY, op, params))
2196
2197 assert verify_fn(result)
2198 else:
2199 result = mods
2200
2201 return result
2202
2203 @staticmethod
2204 def _CheckMods(kind, mods, key_types, item_fn):
2205 """Ensures requested disk/NIC modifications are valid.
2206
2207 """
2208 for (op, _, params) in mods:
2209 assert ht.TDict(params)
2210
2211 # If 'key_types' is an empty dict, we assume we have an
2212 # 'ext' template and thus do not ForceDictType
2213 if key_types:
2214 utils.ForceDictType(params, key_types)
2215
2216 if op == constants.DDM_REMOVE:
2217 if params:
2218 raise errors.OpPrereqError("No settings should be passed when"
2219 " removing a %s" % kind,
2220 errors.ECODE_INVAL)
2221 elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
2222 item_fn(op, params)
2223 else:
2224 raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2225
2226 @staticmethod
2227 def _VerifyDiskModification(op, params):
2228 """Verifies a disk modification.
2229
2230 """
2231 if op == constants.DDM_ADD:
2232 mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
2233 if mode not in constants.DISK_ACCESS_SET:
2234 raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
2235 errors.ECODE_INVAL)
2236
2237 size = params.get(constants.IDISK_SIZE, None)
2238 if size is None:
2239 raise errors.OpPrereqError("Required disk parameter '%s' missing" %
2240 constants.IDISK_SIZE, errors.ECODE_INVAL)
2241
2242 try:
2243 size = int(size)
2244 except (TypeError, ValueError), err:
2245 raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
2246 errors.ECODE_INVAL)
2247
2248 params[constants.IDISK_SIZE] = size
2249 name = params.get(constants.IDISK_NAME, None)
2250 if name is not None and name.lower() == constants.VALUE_NONE:
2251 params[constants.IDISK_NAME] = None
2252
2253 elif op == constants.DDM_MODIFY:
2254 if constants.IDISK_SIZE in params:
2255 raise errors.OpPrereqError("Disk size change not possible, use"
2256 " grow-disk", errors.ECODE_INVAL)
2257 if len(params) > 2:
2258 raise errors.OpPrereqError("Disk modification doesn't support"
2259 " additional arbitrary parameters",
2260 errors.ECODE_INVAL)
2261 name = params.get(constants.IDISK_NAME, None)
2262 if name is not None and name.lower() == constants.VALUE_NONE:
2263 params[constants.IDISK_NAME] = None
2264
2265 @staticmethod
2266 def _VerifyNicModification(op, params):
2267 """Verifies a network interface modification.
2268
2269 """
2270 if op in (constants.DDM_ADD, constants.DDM_MODIFY):
2271 ip = params.get(constants.INIC_IP, None)
2272 name = params.get(constants.INIC_NAME, None)
2273 req_net = params.get(constants.INIC_NETWORK, None)
2274 link = params.get(constants.NIC_LINK, None)
2275 mode = params.get(constants.NIC_MODE, None)
2276 if name is not None and name.lower() == constants.VALUE_NONE:
2277 params[constants.INIC_NAME] = None
2278 if req_net is not None:
2279 if req_net.lower() == constants.VALUE_NONE:
2280 params[constants.INIC_NETWORK] = None
2281 req_net = None
2282 elif link is not None or mode is not None:
2283 raise errors.OpPrereqError("If network is given"
2284 " mode or link should not",
2285 errors.ECODE_INVAL)
2286
2287 if op == constants.DDM_ADD:
2288 macaddr = params.get(constants.INIC_MAC, None)
2289 if macaddr is None:
2290 params[constants.INIC_MAC] = constants.VALUE_AUTO
2291
2292 if ip is not None:
2293 if ip.lower() == constants.VALUE_NONE:
2294 params[constants.INIC_IP] = None
2295 else:
2296 if ip.lower() == constants.NIC_IP_POOL:
2297 if op == constants.DDM_ADD and req_net is None:
2298 raise errors.OpPrereqError("If ip=pool, parameter network"
2299 " cannot be none",
2300 errors.ECODE_INVAL)
2301 else:
2302 if not netutils.IPAddress.IsValid(ip):
2303 raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
2304 errors.ECODE_INVAL)
2305
2306 if constants.INIC_MAC in params:
2307 macaddr = params[constants.INIC_MAC]
2308 if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2309 macaddr = utils.NormalizeAndValidateMac(macaddr)
2310
2311 if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
2312 raise errors.OpPrereqError("'auto' is not a valid MAC address when"
2313 " modifying an existing NIC",
2314 errors.ECODE_INVAL)
2315
2316 def CheckArguments(self):
2317 if not (self.op.nics or self.op.disks or self.op.disk_template or
2318 self.op.hvparams or self.op.beparams or self.op.os_name or
2319 self.op.osparams or self.op.offline is not None or
2320 self.op.runtime_mem or self.op.pnode):
2321 raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
2322
2323 if self.op.hvparams:
2324 CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
2325 "hypervisor", "instance", "cluster")
2326
2327 self.op.disks = self._UpgradeDiskNicMods(
2328 "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
2329 self.op.nics = self._UpgradeDiskNicMods(
2330 "NIC", self.op.nics, opcodes.OpInstanceSetParams.TestNicModifications)
2331
2332 if self.op.disks and self.op.disk_template is not None:
2333 raise errors.OpPrereqError("Disk template conversion and other disk"
2334 " changes not supported at the same time",
2335 errors.ECODE_INVAL)
2336
2337 if (self.op.disk_template and
2338 self.op.disk_template in constants.DTS_INT_MIRROR and
2339 self.op.remote_node is None):
2340 raise errors.OpPrereqError("Changing the disk template to a mirrored"
2341 " one requires specifying a secondary node",
2342 errors.ECODE_INVAL)
2343
2344 # Check NIC modifications
2345 self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
2346 self._VerifyNicModification)
2347
2348 if self.op.pnode:
2349 self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
2350
2351 def ExpandNames(self):
2352 self._ExpandAndLockInstance()
2353 self.needed_locks[locking.LEVEL_NODEGROUP] = []
2354 # Can't even acquire node locks in shared mode as upcoming changes in
2355 # Ganeti 2.6 will start to modify the node object on disk conversion
2356 self.needed_locks[locking.LEVEL_NODE] = []
2357 self.needed_locks[locking.LEVEL_NODE_RES] = []
2358 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2359 # Look node group to look up the ipolicy
2360 self.share_locks[locking.LEVEL_NODEGROUP] = 1
2361
2362 def DeclareLocks(self, level):
2363 if level == locking.LEVEL_NODEGROUP:
2364 assert not self.needed_locks[locking.LEVEL_NODEGROUP]
2365 # Acquire locks for the instance's nodegroups optimistically. Needs
2366 # to be verified in CheckPrereq
2367 self.needed_locks[locking.LEVEL_NODEGROUP] = \
2368 self.cfg.GetInstanceNodeGroups(self.op.instance_name)
2369 elif level == locking.LEVEL_NODE:
2370 self._LockInstancesNodes()
2371 if self.op.disk_template and self.op.remote_node:
2372 self.op.remote_node = ExpandNodeName(self.cfg, self.op.remote_node)
2373 self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
2374 elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
2375 # Copy node locks
2376 self.needed_locks[locking.LEVEL_NODE_RES] = \
2377 CopyLockList(self.needed_locks[locking.LEVEL_NODE])
2378
2379 def BuildHooksEnv(self):
2380 """Build hooks env.
2381
2382 This runs on the master, primary and secondaries.
2383
2384 """
2385 args = {}
2386 if constants.BE_MINMEM in self.be_new:
2387 args["minmem"] = self.be_new[constants.BE_MINMEM]
2388 if constants.BE_MAXMEM in self.be_new:
2389 args["maxmem"] = self.be_new[constants.BE_MAXMEM]
2390 if constants.BE_VCPUS in self.be_new:
2391 args["vcpus"] = self.be_new[constants.BE_VCPUS]
2392 # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
2393 # information at all.
2394
2395 if self._new_nics is not None:
2396 nics = []
2397
2398 for nic in self._new_nics:
2399 n = copy.deepcopy(nic)
2400 nicparams = self.cluster.SimpleFillNIC(n.nicparams)
2401 n.nicparams = nicparams
2402 nics.append(NICToTuple(self, n))
2403
2404 args["nics"] = nics
2405
2406 env = BuildInstanceHookEnvByObject(self, self.instance, override=args)
2407 if self.op.disk_template:
2408 env["NEW_DISK_TEMPLATE"] = self.op.disk_template
2409 if self.op.runtime_mem:
2410 env["RUNTIME_MEMORY"] = self.op.runtime_mem
2411
2412 return env
2413
2414 def BuildHooksNodes(self):
2415 """Build hooks nodes.
2416
2417 """
2418 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
2419 return (nl, nl)
2420
2421 def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
2422 old_params, cluster, pnode):
2423
2424 update_params_dict = dict([(key, params[key])
2425 for key in constants.NICS_PARAMETERS
2426 if key in params])
2427
2428 req_link = update_params_dict.get(constants.NIC_LINK, None)
2429 req_mode = update_params_dict.get(constants.NIC_MODE, None)
2430
2431 new_net_uuid = None
2432 new_net_uuid_or_name = params.get(constants.INIC_NETWORK, old_net_uuid)
2433 if new_net_uuid_or_name:
2434 new_net_uuid = self.cfg.LookupNetwork(new_net_uuid_or_name)
2435 new_net_obj = self.cfg.GetNetwork(new_net_uuid)
2436
2437 if old_net_uuid:
2438 old_net_obj = self.cfg.GetNetwork(old_net_uuid)
2439
2440 if new_net_uuid:
2441 netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode)
2442 if not netparams:
2443 raise errors.OpPrereqError("No netparams found for the network"
2444 " %s, probably not connected" %
2445 new_net_obj.name, errors.ECODE_INVAL)
2446 new_params = dict(netparams)
2447 else:
2448 new_params = GetUpdatedParams(old_params, update_params_dict)
2449
2450 utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
2451
2452 new_filled_params = cluster.SimpleFillNIC(new_params)
2453 objects.NIC.CheckParameterSyntax(new_filled_params)
2454
2455 new_mode = new_filled_params[constants.NIC_MODE]
2456 if new_mode == constants.NIC_MODE_BRIDGED:
2457 bridge = new_filled_params[constants.NIC_LINK]
2458 msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
2459 if msg:
2460 msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
2461 if self.op.force:
2462 self.warn.append(msg)
2463 else:
2464 raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
2465
2466 elif new_mode == constants.NIC_MODE_ROUTED:
2467 ip = params.get(constants.INIC_IP, old_ip)
2468 if ip is None:
2469 raise errors.OpPrereqError("Cannot set the NIC IP address to None"
2470 " on a routed NIC", errors.ECODE_INVAL)
2471
2472 elif new_mode == constants.NIC_MODE_OVS:
2473 # TODO: check OVS link
2474 self.LogInfo("OVS links are currently not checked for correctness")
2475
2476 if constants.INIC_MAC in params:
2477 mac = params[constants.INIC_MAC]
2478 if mac is None:
2479 raise errors.OpPrereqError("Cannot unset the NIC MAC address",
2480 errors.ECODE_INVAL)
2481 elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2482 # otherwise generate the MAC address
2483 params[constants.INIC_MAC] = \
2484 self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2485 else:
2486 # or validate/reserve the current one
2487 try:
2488 self.cfg.ReserveMAC(mac, self.proc.GetECId())
2489 except errors.ReservationError:
2490 raise errors.OpPrereqError("MAC address '%s' already in use"
2491 " in cluster" % mac,
2492 errors.ECODE_NOTUNIQUE)
2493 elif new_net_uuid != old_net_uuid:
2494
2495 def get_net_prefix(net_uuid):
2496 mac_prefix = None
2497 if net_uuid:
2498 nobj = self.cfg.GetNetwork(net_uuid)
2499 mac_prefix = nobj.mac_prefix
2500
2501 return mac_prefix
2502
2503 new_prefix = get_net_prefix(new_net_uuid)
2504 old_prefix = get_net_prefix(old_net_uuid)
2505 if old_prefix != new_prefix:
2506 params[constants.INIC_MAC] = \
2507 self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2508
2509 # if there is a change in (ip, network) tuple
2510 new_ip = params.get(constants.INIC_IP, old_ip)
2511 if (new_ip, new_net_uuid) != (old_ip, old_net_uuid):
2512 if new_ip:
2513 # if IP is pool then require a network and generate one IP
2514 if new_ip.lower() == constants.NIC_IP_POOL:
2515 if new_net_uuid:
2516 try:
2517 new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId())
2518 except errors.ReservationError:
2519 raise errors.OpPrereqError("Unable to get a free IP"
2520 " from the address pool",
2521 errors.ECODE_STATE)
2522 self.LogInfo("Chose IP %s from network %s",
2523 new_ip,
2524 new_net_obj.name)
2525 params[constants.INIC_IP] = new_ip
2526 else:
2527 raise errors.OpPrereqError("ip=pool, but no network found",
2528 errors.ECODE_INVAL)
2529 # Reserve new IP if in the new network if any
2530 elif new_net_uuid:
2531 try:
2532 self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId())
2533 self.LogInfo("Reserving IP %s in network %s",
2534 new_ip, new_net_obj.name)
2535 except errors.ReservationError:
2536 raise errors.OpPrereqError("IP %s not available in network %s" %
2537 (new_ip, new_net_obj.name),
2538 errors.ECODE_NOTUNIQUE)
2539 # new network is None so check if new IP is a conflicting IP
2540 elif self.op.conflicts_check:
2541 _CheckForConflictingIp(self, new_ip, pnode)
2542
2543 # release old IP if old network is not None
2544 if old_ip and old_net_uuid:
2545 try:
2546 self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId())
2547 except errors.AddressPoolError:
2548 logging.warning("Release IP %s not contained in network %s",
2549 old_ip, old_net_obj.name)
2550
2551 # there are no changes in (ip, network) tuple and old network is not None
2552 elif (old_net_uuid is not None and
2553 (req_link is not None or req_mode is not None)):
2554 raise errors.OpPrereqError("Not allowed to change link or mode of"
2555 " a NIC that is connected to a network",
2556 errors.ECODE_INVAL)
2557
2558 private.params = new_params
2559 private.filled = new_filled_params
2560
2561 def _PreCheckDiskTemplate(self, pnode_info):
2562 """CheckPrereq checks related to a new disk template."""
2563 # Arguments are passed to avoid configuration lookups
2564 instance = self.instance
2565 pnode = instance.primary_node
2566 cluster = self.cluster
2567 if instance.disk_template == self.op.disk_template:
2568 raise errors.OpPrereqError("Instance already has disk template %s" %
2569 instance.disk_template, errors.ECODE_INVAL)
2570
2571 if (instance.disk_template,
2572 self.op.disk_template) not in self._DISK_CONVERSIONS:
2573 raise errors.OpPrereqError("Unsupported disk template conversion from"
2574 " %s to %s" % (instance.disk_template,
2575 self.op.disk_template),
2576 errors.ECODE_INVAL)
2577 CheckInstanceState(self, instance, INSTANCE_DOWN,
2578 msg="cannot change disk template")
2579 if self.op.disk_template in constants.DTS_INT_MIRROR:
2580 if self.op.remote_node == pnode:
2581 raise errors.OpPrereqError("Given new secondary node %s is the same"
2582 " as the primary node of the instance" %
2583 self.op.remote_node, errors.ECODE_STATE)
2584 CheckNodeOnline(self, self.op.remote_node)
2585 CheckNodeNotDrained(self, self.op.remote_node)
2586 # FIXME: here we assume that the old instance type is DT_PLAIN
2587 assert instance.disk_template == constants.DT_PLAIN
2588 disks = [{constants.IDISK_SIZE: d.size,
2589 constants.IDISK_VG: d.logical_id[0]}
2590 for d in instance.disks]
2591 required = ComputeDiskSizePerVG(self.op.disk_template, disks)
2592 CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
2593
2594 snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
2595 snode_group = self.cfg.GetNodeGroup(snode_info.group)
2596 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2597 snode_group)
2598 CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info, self.cfg,
2599 ignore=self.op.ignore_ipolicy)
2600 if pnode_info.group != snode_info.group:
2601 self.LogWarning("The primary and secondary nodes are in two"
2602 " different node groups; the disk parameters"
2603 " from the first disk's node group will be"
2604 " used")
2605
2606 if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
2607 # Make sure none of the nodes require exclusive storage
2608 nodes = [pnode_info]
2609 if self.op.disk_template in constants.DTS_INT_MIRROR:
2610 assert snode_info
2611 nodes.append(snode_info)
2612 has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
2613 if compat.any(map(has_es, nodes)):
2614 errmsg = ("Cannot convert disk template from %s to %s when exclusive"
2615 " storage is enabled" % (instance.disk_template,
2616 self.op.disk_template))
2617 raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
2618
2619 def CheckPrereq(self):
2620 """Check prerequisites.
2621
2622 This only checks the instance list against the existing names.
2623
2624 """
2625 assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
2626 instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
2627
2628 cluster = self.cluster = self.cfg.GetClusterInfo()
2629 assert self.instance is not None, \
2630 "Cannot retrieve locked instance %s" % self.op.instance_name
2631
2632 pnode = instance.primary_node
2633
2634 self.warn = []
2635
2636 if (self.op.pnode is not None and self.op.pnode != pnode and
2637 not self.op.force):
2638 # verify that the instance is not up
2639 instance_info = self.rpc.call_instance_info(pnode, instance.name,
2640 instance.hypervisor)
2641 if instance_info.fail_msg:
2642 self.warn.append("Can't get instance runtime information: %s" %
2643 instance_info.fail_msg)
2644 elif instance_info.payload:
2645 raise errors.OpPrereqError("Instance is still running on %s" % pnode,
2646 errors.ECODE_STATE)
2647
2648 assert pnode in self.owned_locks(locking.LEVEL_NODE)
2649 nodelist = list(instance.all_nodes)
2650 pnode_info = self.cfg.GetNodeInfo(pnode)
2651 self.diskparams = self.cfg.GetInstanceDiskParams(instance)
2652
2653 #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
2654 assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
2655 group_info = self.cfg.GetNodeGroup(pnode_info.group)
2656
2657 # dictionary with instance information after the modification
2658 ispec = {}
2659
2660 # Check disk modifications. This is done here and not in CheckArguments
2661 # (as with NICs), because we need to know the instance's disk template
2662 if instance.disk_template == constants.DT_EXT:
2663 self._CheckMods("disk", self.op.disks, {},
2664 self._VerifyDiskModification)
2665 else:
2666 self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
2667 self._VerifyDiskModification)
2668
2669 # Prepare disk/NIC modifications
2670 self.diskmod = _PrepareContainerMods(self.op.disks, None)
2671 self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
2672
2673 # Check the validity of the `provider' parameter
2674 if instance.disk_template in constants.DT_EXT:
2675 for mod in self.diskmod:
2676 ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2677 if mod[0] == constants.DDM_ADD:
2678 if ext_provider is None:
2679 raise errors.OpPrereqError("Instance template is '%s' and parameter"
2680 " '%s' missing, during disk add" %
2681 (constants.DT_EXT,
2682 constants.IDISK_PROVIDER),
2683 errors.ECODE_NOENT)
2684 elif mod[0] == constants.DDM_MODIFY:
2685 if ext_provider:
2686 raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
2687 " modification" %
2688 constants.IDISK_PROVIDER,
2689 errors.ECODE_INVAL)
2690 else:
2691 for mod in self.diskmod:
2692 ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2693 if ext_provider is not None:
2694 raise errors.OpPrereqError("Parameter '%s' is only valid for"
2695 " instances of type '%s'" %
2696 (constants.IDISK_PROVIDER,
2697 constants.DT_EXT),
2698 errors.ECODE_INVAL)
2699
2700 # OS change
2701 if self.op.os_name and not self.op.force:
2702 CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
2703 self.op.force_variant)
2704 instance_os = self.op.os_name
2705 else:
2706 instance_os = instance.os
2707
2708 assert not (self.op.disk_template and self.op.disks), \
2709 "Can't modify disk template and apply disk changes at the same time"
2710
2711 if self.op.disk_template:
2712 self._PreCheckDiskTemplate(pnode_info)
2713
2714 # hvparams processing
2715 if self.op.hvparams:
2716 hv_type = instance.hypervisor
2717 i_hvdict = GetUpdatedParams(instance.hvparams, self.op.hvparams)
2718 utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
2719 hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
2720
2721 # local check
2722 hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
2723 CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
2724 self.hv_proposed = self.hv_new = hv_new # the new actual values
2725 self.hv_inst = i_hvdict # the new dict (without defaults)
2726 else:
2727 self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
2728 instance.hvparams)
2729 self.hv_new = self.hv_inst = {}
2730
2731 # beparams processing
2732 if self.op.beparams:
2733 i_bedict = GetUpdatedParams(instance.beparams, self.op.beparams,
2734 use_none=True)
2735 objects.UpgradeBeParams(i_bedict)
2736 utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
2737 be_new = cluster.SimpleFillBE(i_bedict)
2738 self.be_proposed = self.be_new = be_new # the new actual values
2739 self.be_inst = i_bedict # the new dict (without defaults)
2740 else:
2741 self.be_new = self.be_inst = {}
2742 self.be_proposed = cluster.SimpleFillBE(instance.beparams)
2743 be_old = cluster.FillBE(instance)
2744
2745 # CPU param validation -- checking every time a parameter is
2746 # changed to cover all cases where either CPU mask or vcpus have
2747 # changed
2748 if (constants.BE_VCPUS in self.be_proposed and
2749 constants.HV_CPU_MASK in self.hv_proposed):
2750 cpu_list = \
2751 utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
2752 # Verify mask is consistent with number of vCPUs. Can skip this
2753 # test if only 1 entry in the CPU mask, which means same mask
2754 # is applied to all vCPUs.
2755 if (len(cpu_list) > 1 and
2756 len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
2757 raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
2758 " CPU mask [%s]" %
2759 (self.be_proposed[constants.BE_VCPUS],
2760 self.hv_proposed[constants.HV_CPU_MASK]),
2761 errors.ECODE_INVAL)
2762
2763 # Only perform this test if a new CPU mask is given
2764 if constants.HV_CPU_MASK in self.hv_new:
2765 # Calculate the largest CPU number requested
2766 max_requested_cpu = max(map(max, cpu_list))
2767 # Check that all of the instance's nodes have enough physical CPUs to
2768 # satisfy the requested CPU mask
2769 _CheckNodesPhysicalCPUs(self, instance.all_nodes,
2770 max_requested_cpu + 1, instance.hypervisor)
2771
2772 # osparams processing
2773 if self.op.osparams:
2774 i_osdict = GetUpdatedParams(instance.osparams, self.op.osparams)
2775 CheckOSParams(self, True, nodelist, instance_os, i_osdict)
2776 self.os_inst = i_osdict # the new dict (without defaults)
2777 else:
2778 self.os_inst = {}
2779
2780 #TODO(dynmem): do the appropriate check involving MINMEM
2781 if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
2782 be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
2783 mem_check_list = [pnode]
2784 if be_new[constants.BE_AUTO_BALANCE]:
2785 # either we changed auto_balance to yes or it was from before
2786 mem_check_list.extend(instance.secondary_nodes)
2787 instance_info = self.rpc.call_instance_info(pnode, instance.name,
2788 instance.hypervisor)
2789 nodeinfo = self.rpc.call_node_info(mem_check_list, None,
2790 [instance.hypervisor], False)
2791 pninfo = nodeinfo[pnode]
2792 msg = pninfo.fail_msg
2793 if msg:
2794 # Assume the primary node is unreachable and go ahead
2795 self.warn.append("Can't get info from primary node %s: %s" %
2796 (pnode, msg))
2797 else:
2798 (_, _, (pnhvinfo, )) = pninfo.payload
2799 if not isinstance(pnhvinfo.get("memory_free", None), int):
2800 self.warn.append("Node data from primary node %s doesn't contain"
2801 " free memory information" % pnode)
2802 elif instance_info.fail_msg:
2803 self.warn.append("Can't get instance runtime information: %s" %
2804 instance_info.fail_msg)
2805 else:
2806 if instance_info.payload:
2807 current_mem = int(instance_info.payload["memory"])
2808 else:
2809 # Assume instance not running
2810 # (there is a slight race condition here, but it's not very
2811 # probable, and we have no other way to check)
2812 # TODO: Describe race condition
2813 current_mem = 0
2814 #TODO(dynmem): do the appropriate check involving MINMEM
2815 miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
2816 pnhvinfo["memory_free"])
2817 if miss_mem > 0:
2818 raise errors.OpPrereqError("This change will prevent the instance"
2819 " from starting, due to %d MB of memory"
2820 " missing on its primary node" %
2821 miss_mem, errors.ECODE_NORES)
2822
2823 if be_new[constants.BE_AUTO_BALANCE]:
2824 for node, nres in nodeinfo.items():
2825 if node not in instance.secondary_nodes:
2826 continue
2827 nres.Raise("Can't get info from secondary node %s" % node,
2828 prereq=True, ecode=errors.ECODE_STATE)
2829 (_, _, (nhvinfo, )) = nres.payload
2830 if not isinstance(nhvinfo.get("memory_free", None), int):
2831 raise errors.OpPrereqError("Secondary node %s didn't return free"
2832 " memory information" % node,
2833 errors.ECODE_STATE)
2834 #TODO(dynmem): do the appropriate check involving MINMEM
2835 elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
2836 raise errors.OpPrereqError("This change will prevent the instance"
2837 " from failover to its secondary node"
2838 " %s, due to not enough memory" % node,
2839 errors.ECODE_STATE)
2840
2841 if self.op.runtime_mem:
2842 remote_info = self.rpc.call_instance_info(instance.primary_node,
2843 instance.name,
2844 instance.hypervisor)
2845 remote_info.Raise("Error checking node %s" % instance.primary_node)
2846 if not remote_info.payload: # not running already
2847 raise errors.OpPrereqError("Instance %s is not running" %
2848 instance.name, errors.ECODE_STATE)
2849
2850 current_memory = remote_info.payload["memory"]
2851 if (not self.op.force and
2852 (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
2853 self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
2854 raise errors.OpPrereqError("Instance %s must have memory between %d"
2855 " and %d MB of memory unless --force is"
2856 " given" %
2857 (instance.name,
2858 self.be_proposed[constants.BE_MINMEM],
2859 self.be_proposed[constants.BE_MAXMEM]),
2860 errors.ECODE_INVAL)
2861
2862 delta = self.op.runtime_mem - current_memory
2863 if delta > 0:
2864 CheckNodeFreeMemory(self, instance.primary_node,
2865 "ballooning memory for instance %s" %
2866 instance.name, delta, instance.hypervisor)
2867
2868 if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
2869 raise errors.OpPrereqError("Disk operations not supported for"
2870 " diskless instances", errors.ECODE_INVAL)
2871
2872 def _PrepareNicCreate(_, params, private):
2873 self._PrepareNicModification(params, private, None, None,
2874 {}, cluster, pnode)
2875 return (None, None)
2876
2877 def _PrepareNicMod(_, nic, params, private):
2878 self._PrepareNicModification(params, private, nic.ip, nic.network,
2879 nic.nicparams, cluster, pnode)
2880 return None
2881
2882 def _PrepareNicRemove(_, params, __):
2883 ip = params.ip
2884 net = params.network
2885 if net is not None and ip is not None:
2886 self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
2887
2888 # Verify NIC changes (operating on copy)
2889 nics = instance.nics[:]
2890 _ApplyContainerMods("NIC", nics, None, self.nicmod,
2891 _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
2892 if len(nics) > constants.MAX_NICS:
2893 raise errors.OpPrereqError("Instance has too many network interfaces"
2894 " (%d), cannot add more" % constants.MAX_NICS,
2895 errors.ECODE_STATE)
2896
2897 def _PrepareDiskMod(_, disk, params, __):
2898 disk.name = params.get(constants.IDISK_NAME, None)
2899
2900 # Verify disk changes (operating on a copy)
2901 disks = copy.deepcopy(instance.disks)
2902 _ApplyContainerMods("disk", disks, None, self.diskmod, None,
2903 _PrepareDiskMod, None)
2904 utils.ValidateDeviceNames("disk", disks)
2905 if len(disks) > constants.MAX_DISKS:
2906 raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
2907 " more" % constants.MAX_DISKS,
2908 errors.ECODE_STATE)
2909 disk_sizes = [disk.size for disk in instance.disks]
2910 disk_sizes.extend(params["size"] for (op, idx, params, private) in
2911 self.diskmod if op == constants.DDM_ADD)
2912 ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
2913 ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
2914
2915 if self.op.offline is not None and self.op.offline:
2916 CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE,
2917 msg="can't change to offline")
2918
2919 # Pre-compute NIC changes (necessary to use result in hooks)
2920 self._nic_chgdesc = []
2921 if self.nicmod:
2922 # Operate on copies as this is still in prereq
2923 nics = [nic.Copy() for nic in instance.nics]
2924 _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
2925 self._CreateNewNic, self._ApplyNicMods, None)
2926 # Verify that NIC names are unique and valid
2927 utils.ValidateDeviceNames("NIC", nics)
2928 self._new_nics = nics
2929 ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
2930 else:
2931 self._new_nics = None
2932 ispec[constants.ISPEC_NIC_COUNT] = len(instance.nics)
2933
2934 if not self.op.ignore_ipolicy:
2935 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2936 group_info)
2937
2938 # Fill ispec with backend parameters
2939 ispec[constants.ISPEC_SPINDLE_USE] = \
2940 self.be_new.get(constants.BE_SPINDLE_USE, None)
2941 ispec[constants.ISPEC_CPU_COUNT] = self.be_new.get(constants.BE_VCPUS,
2942 None)
2943
2944 # Copy ispec to verify parameters with min/max values separately
2945 if self.op.disk_template:
2946 new_disk_template = self.op.disk_template
2947 else:
2948 new_disk_template = instance.disk_template
2949 ispec_max = ispec.copy()
2950 ispec_max[constants.ISPEC_MEM_SIZE] = \
2951 self.be_new.get(constants.BE_MAXMEM, None)
2952 res_max = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_max,
2953 new_disk_template)
2954 ispec_min = ispec.copy()
2955 ispec_min[constants.ISPEC_MEM_SIZE] = \
2956 self.be_new.get(constants.BE_MINMEM, None)
2957 res_min = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
2958 new_disk_template)
2959
2960 if (res_max or res_min):
2961 # FIXME: Improve error message by including information about whether
2962 # the upper or lower limit of the parameter fails the ipolicy.
2963 msg = ("Instance allocation to group %s (%s) violates policy: %s" %
2964 (group_info, group_info.name,
2965 utils.CommaJoin(set(res_max + res_min))))
2966 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2967
2968 def _ConvertPlainToDrbd(self, feedback_fn):
2969 """Converts an instance from plain to drbd.
2970
2971 """
2972 feedback_fn("Converting template to drbd")
2973 instance = self.instance
2974 pnode = instance.primary_node
2975 snode = self.op.remote_node
2976
2977 assert instance.disk_template == constants.DT_PLAIN
2978
2979 # create a fake disk info for _GenerateDiskTemplate
2980 disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
2981 constants.IDISK_VG: d.logical_id[0],
2982 constants.IDISK_NAME: d.name}
2983 for d in instance.disks]
2984 new_disks = GenerateDiskTemplate(self, self.op.disk_template,
2985 instance.name, pnode, [snode],
2986 disk_info, None, None, 0, feedback_fn,
2987 self.diskparams)
2988 anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
2989 self.diskparams)
2990 p_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, pnode)
2991 s_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, snode)
2992 info = GetInstanceInfoText(instance)
2993 feedback_fn("Creating additional volumes...")
2994 # first, create the missing data and meta devices
2995 for disk in anno_disks:
2996 # unfortunately this is... not too nice
2997 CreateSingleBlockDev(self, pnode, instance, disk.children[1],