(ipfix) Allow NICs with mode=routed and ip=none
[ganeti-local] / 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     # MAC address verification
209     mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
210     if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
211       mac = utils.NormalizeAndValidateMac(mac)
212
213       try:
214         # TODO: We need to factor this out
215         cfg.ReserveMAC(mac, ec_id)
216       except errors.ReservationError:
217         raise errors.OpPrereqError("MAC address %s already in use"
218                                    " in cluster" % mac,
219                                    errors.ECODE_NOTUNIQUE)
220
221     #  Build nic parameters
222     nicparams = {}
223     if nic_mode_req:
224       nicparams[constants.NIC_MODE] = nic_mode
225     if link:
226       nicparams[constants.NIC_LINK] = link
227
228     check_params = cluster.SimpleFillNIC(nicparams)
229     objects.NIC.CheckParameterSyntax(check_params)
230     net_uuid = cfg.LookupNetwork(net)
231     name = nic.get(constants.INIC_NAME, None)
232     if name is not None and name.lower() == constants.VALUE_NONE:
233       name = None
234     nic_obj = objects.NIC(mac=mac, ip=nic_ip, name=name,
235                           network=net_uuid, nicparams=nicparams)
236     nic_obj.uuid = cfg.GenerateUniqueID(ec_id)
237     nics.append(nic_obj)
238
239   return nics
240
241
242 def _CheckForConflictingIp(lu, ip, node):
243   """In case of conflicting IP address raise error.
244
245   @type ip: string
246   @param ip: IP address
247   @type node: string
248   @param node: node name
249
250   """
251   (conf_net, _) = lu.cfg.CheckIPInNodeGroup(ip, node)
252   if conf_net is not None:
253     raise errors.OpPrereqError(("The requested IP address (%s) belongs to"
254                                 " network %s, but the target NIC does not." %
255                                 (ip, conf_net)),
256                                errors.ECODE_STATE)
257
258   return (None, None)
259
260
261 def _ComputeIPolicyInstanceSpecViolation(
262   ipolicy, instance_spec, disk_template,
263   _compute_fn=ComputeIPolicySpecViolation):
264   """Compute if instance specs meets the specs of ipolicy.
265
266   @type ipolicy: dict
267   @param ipolicy: The ipolicy to verify against
268   @param instance_spec: dict
269   @param instance_spec: The instance spec to verify
270   @type disk_template: string
271   @param disk_template: the disk template of the instance
272   @param _compute_fn: The function to verify ipolicy (unittest only)
273   @see: L{ComputeIPolicySpecViolation}
274
275   """
276   mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
277   cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
278   disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
279   disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
280   nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
281   spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
282
283   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
284                      disk_sizes, spindle_use, disk_template)
285
286
287 def _CheckOSVariant(os_obj, name):
288   """Check whether an OS name conforms to the os variants specification.
289
290   @type os_obj: L{objects.OS}
291   @param os_obj: OS object to check
292   @type name: string
293   @param name: OS name passed by the user, to check for validity
294
295   """
296   variant = objects.OS.GetVariant(name)
297   if not os_obj.supported_variants:
298     if variant:
299       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
300                                  " passed)" % (os_obj.name, variant),
301                                  errors.ECODE_INVAL)
302     return
303   if not variant:
304     raise errors.OpPrereqError("OS name must include a variant",
305                                errors.ECODE_INVAL)
306
307   if variant not in os_obj.supported_variants:
308     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
309
310
311 class LUInstanceCreate(LogicalUnit):
312   """Create an instance.
313
314   """
315   HPATH = "instance-add"
316   HTYPE = constants.HTYPE_INSTANCE
317   REQ_BGL = False
318
319   def CheckArguments(self):
320     """Check arguments.
321
322     """
323     # do not require name_check to ease forward/backward compatibility
324     # for tools
325     if self.op.no_install and self.op.start:
326       self.LogInfo("No-installation mode selected, disabling startup")
327       self.op.start = False
328     # validate/normalize the instance name
329     self.op.instance_name = \
330       netutils.Hostname.GetNormalizedName(self.op.instance_name)
331
332     if self.op.ip_check and not self.op.name_check:
333       # TODO: make the ip check more flexible and not depend on the name check
334       raise errors.OpPrereqError("Cannot do IP address check without a name"
335                                  " check", errors.ECODE_INVAL)
336
337     # check nics' parameter names
338     for nic in self.op.nics:
339       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
340     # check that NIC's parameters names are unique and valid
341     utils.ValidateDeviceNames("NIC", self.op.nics)
342
343     # check that disk's names are unique and valid
344     utils.ValidateDeviceNames("disk", self.op.disks)
345
346     cluster = self.cfg.GetClusterInfo()
347     if not self.op.disk_template in cluster.enabled_disk_templates:
348       raise errors.OpPrereqError("Cannot create an instance with disk template"
349                                  " '%s', because it is not enabled in the"
350                                  " cluster. Enabled disk templates are: %s." %
351                                  (self.op.disk_template,
352                                   ",".join(cluster.enabled_disk_templates)))
353
354     # check disks. parameter names and consistent adopt/no-adopt strategy
355     has_adopt = has_no_adopt = False
356     for disk in self.op.disks:
357       if self.op.disk_template != constants.DT_EXT:
358         utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
359       if constants.IDISK_ADOPT in disk:
360         has_adopt = True
361       else:
362         has_no_adopt = True
363     if has_adopt and has_no_adopt:
364       raise errors.OpPrereqError("Either all disks are adopted or none is",
365                                  errors.ECODE_INVAL)
366     if has_adopt:
367       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
368         raise errors.OpPrereqError("Disk adoption is not supported for the"
369                                    " '%s' disk template" %
370                                    self.op.disk_template,
371                                    errors.ECODE_INVAL)
372       if self.op.iallocator is not None:
373         raise errors.OpPrereqError("Disk adoption not allowed with an"
374                                    " iallocator script", errors.ECODE_INVAL)
375       if self.op.mode == constants.INSTANCE_IMPORT:
376         raise errors.OpPrereqError("Disk adoption not allowed for"
377                                    " instance import", errors.ECODE_INVAL)
378     else:
379       if self.op.disk_template in constants.DTS_MUST_ADOPT:
380         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
381                                    " but no 'adopt' parameter given" %
382                                    self.op.disk_template,
383                                    errors.ECODE_INVAL)
384
385     self.adopt_disks = has_adopt
386
387     # instance name verification
388     if self.op.name_check:
389       self.hostname1 = _CheckHostnameSane(self, self.op.instance_name)
390       self.op.instance_name = self.hostname1.name
391       # used in CheckPrereq for ip ping check
392       self.check_ip = self.hostname1.ip
393     else:
394       self.check_ip = None
395
396     # file storage checks
397     if (self.op.file_driver and
398         not self.op.file_driver in constants.FILE_DRIVER):
399       raise errors.OpPrereqError("Invalid file driver name '%s'" %
400                                  self.op.file_driver, errors.ECODE_INVAL)
401
402     # set default file_driver if unset and required
403     if (not self.op.file_driver and
404         self.op.disk_template in [constants.DT_FILE,
405                                   constants.DT_SHARED_FILE]):
406       self.op.file_driver = constants.FD_DEFAULT
407
408     if self.op.disk_template == constants.DT_FILE:
409       opcodes.RequireFileStorage()
410     elif self.op.disk_template == constants.DT_SHARED_FILE:
411       opcodes.RequireSharedFileStorage()
412
413     ### Node/iallocator related checks
414     CheckIAllocatorOrNode(self, "iallocator", "pnode")
415
416     if self.op.pnode is not None:
417       if self.op.disk_template in constants.DTS_INT_MIRROR:
418         if self.op.snode is None:
419           raise errors.OpPrereqError("The networked disk templates need"
420                                      " a mirror node", errors.ECODE_INVAL)
421       elif self.op.snode:
422         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
423                         " template")
424         self.op.snode = None
425
426     _CheckOpportunisticLocking(self.op)
427
428     self._cds = GetClusterDomainSecret()
429
430     if self.op.mode == constants.INSTANCE_IMPORT:
431       # On import force_variant must be True, because if we forced it at
432       # initial install, our only chance when importing it back is that it
433       # works again!
434       self.op.force_variant = True
435
436       if self.op.no_install:
437         self.LogInfo("No-installation mode has no effect during import")
438
439     elif self.op.mode == constants.INSTANCE_CREATE:
440       if self.op.os_type is None:
441         raise errors.OpPrereqError("No guest OS specified",
442                                    errors.ECODE_INVAL)
443       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
444         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
445                                    " installation" % self.op.os_type,
446                                    errors.ECODE_STATE)
447       if self.op.disk_template is None:
448         raise errors.OpPrereqError("No disk template specified",
449                                    errors.ECODE_INVAL)
450
451     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
452       # Check handshake to ensure both clusters have the same domain secret
453       src_handshake = self.op.source_handshake
454       if not src_handshake:
455         raise errors.OpPrereqError("Missing source handshake",
456                                    errors.ECODE_INVAL)
457
458       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
459                                                            src_handshake)
460       if errmsg:
461         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
462                                    errors.ECODE_INVAL)
463
464       # Load and check source CA
465       self.source_x509_ca_pem = self.op.source_x509_ca
466       if not self.source_x509_ca_pem:
467         raise errors.OpPrereqError("Missing source X509 CA",
468                                    errors.ECODE_INVAL)
469
470       try:
471         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
472                                                     self._cds)
473       except OpenSSL.crypto.Error, err:
474         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
475                                    (err, ), errors.ECODE_INVAL)
476
477       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
478       if errcode is not None:
479         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
480                                    errors.ECODE_INVAL)
481
482       self.source_x509_ca = cert
483
484       src_instance_name = self.op.source_instance_name
485       if not src_instance_name:
486         raise errors.OpPrereqError("Missing source instance name",
487                                    errors.ECODE_INVAL)
488
489       self.source_instance_name = \
490         netutils.GetHostname(name=src_instance_name).name
491
492     else:
493       raise errors.OpPrereqError("Invalid instance creation mode %r" %
494                                  self.op.mode, errors.ECODE_INVAL)
495
496   def ExpandNames(self):
497     """ExpandNames for CreateInstance.
498
499     Figure out the right locks for instance creation.
500
501     """
502     self.needed_locks = {}
503
504     instance_name = self.op.instance_name
505     # this is just a preventive check, but someone might still add this
506     # instance in the meantime, and creation will fail at lock-add time
507     if instance_name in self.cfg.GetInstanceList():
508       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
509                                  instance_name, errors.ECODE_EXISTS)
510
511     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
512
513     if self.op.iallocator:
514       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
515       # specifying a group on instance creation and then selecting nodes from
516       # that group
517       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
518       self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
519
520       if self.op.opportunistic_locking:
521         self.opportunistic_locks[locking.LEVEL_NODE] = 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     # Optimistically acquire shared group locks (we're reading the
558     # configuration).  We can't just call GetInstanceNodeGroups, because the
559     # instance doesn't exist yet. Therefore we lock all node groups of all
560     # nodes we have.
561     if self.needed_locks[locking.LEVEL_NODE] == locking.ALL_SET:
562       # In the case we lock all nodes for opportunistic allocation, we have no
563       # choice than to lock all groups, because they're allocated before nodes.
564       # This is sad, but true. At least we release all those we don't need in
565       # CheckPrereq later.
566       self.needed_locks[locking.LEVEL_NODEGROUP] = locking.ALL_SET
567     else:
568       self.needed_locks[locking.LEVEL_NODEGROUP] = \
569         list(self.cfg.GetNodeGroupsFromNodes(
570           self.needed_locks[locking.LEVEL_NODE]))
571     self.share_locks[locking.LEVEL_NODEGROUP] = 1
572
573   def DeclareLocks(self, level):
574     if level == locking.LEVEL_NODE_RES and \
575       self.opportunistic_locks[locking.LEVEL_NODE]:
576       # Even when using opportunistic locking, we require the same set of
577       # NODE_RES locks as we got NODE locks
578       self.needed_locks[locking.LEVEL_NODE_RES] = \
579         self.owned_locks(locking.LEVEL_NODE)
580
581   def _RunAllocator(self):
582     """Run the allocator based on input opcode.
583
584     """
585     if self.op.opportunistic_locking:
586       # Only consider nodes for which a lock is held
587       node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
588     else:
589       node_whitelist = None
590
591     #TODO Export network to iallocator so that it chooses a pnode
592     #     in a nodegroup that has the desired network connected to
593     req = _CreateInstanceAllocRequest(self.op, self.disks,
594                                       self.nics, self.be_full,
595                                       node_whitelist)
596     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
597
598     ial.Run(self.op.iallocator)
599
600     if not ial.success:
601       # When opportunistic locks are used only a temporary failure is generated
602       if self.op.opportunistic_locking:
603         ecode = errors.ECODE_TEMP_NORES
604       else:
605         ecode = errors.ECODE_NORES
606
607       raise errors.OpPrereqError("Can't compute nodes using"
608                                  " iallocator '%s': %s" %
609                                  (self.op.iallocator, ial.info),
610                                  ecode)
611
612     self.op.pnode = ial.result[0]
613     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
614                  self.op.instance_name, self.op.iallocator,
615                  utils.CommaJoin(ial.result))
616
617     assert req.RequiredNodes() in (1, 2), "Wrong node count from iallocator"
618
619     if req.RequiredNodes() == 2:
620       self.op.snode = ial.result[1]
621
622   def BuildHooksEnv(self):
623     """Build hooks env.
624
625     This runs on master, primary and secondary nodes of the instance.
626
627     """
628     env = {
629       "ADD_MODE": self.op.mode,
630       }
631     if self.op.mode == constants.INSTANCE_IMPORT:
632       env["SRC_NODE"] = self.op.src_node
633       env["SRC_PATH"] = self.op.src_path
634       env["SRC_IMAGES"] = self.src_images
635
636     env.update(BuildInstanceHookEnv(
637       name=self.op.instance_name,
638       primary_node=self.op.pnode,
639       secondary_nodes=self.secondaries,
640       status=self.op.start,
641       os_type=self.op.os_type,
642       minmem=self.be_full[constants.BE_MINMEM],
643       maxmem=self.be_full[constants.BE_MAXMEM],
644       vcpus=self.be_full[constants.BE_VCPUS],
645       nics=NICListToTuple(self, self.nics),
646       disk_template=self.op.disk_template,
647       disks=[(d[constants.IDISK_NAME], d.get("uuid", ""),
648               d[constants.IDISK_SIZE], d[constants.IDISK_MODE], {})
649              for d in self.disks],
650       bep=self.be_full,
651       hvp=self.hv_full,
652       hypervisor_name=self.op.hypervisor,
653       tags=self.op.tags,
654       ))
655
656     return env
657
658   def BuildHooksNodes(self):
659     """Build hooks nodes.
660
661     """
662     nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
663     return nl, nl
664
665   def _ReadExportInfo(self):
666     """Reads the export information from disk.
667
668     It will override the opcode source node and path with the actual
669     information, if these two were not specified before.
670
671     @return: the export information
672
673     """
674     assert self.op.mode == constants.INSTANCE_IMPORT
675
676     src_node = self.op.src_node
677     src_path = self.op.src_path
678
679     if src_node is None:
680       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
681       exp_list = self.rpc.call_export_list(locked_nodes)
682       found = False
683       for node in exp_list:
684         if exp_list[node].fail_msg:
685           continue
686         if src_path in exp_list[node].payload:
687           found = True
688           self.op.src_node = src_node = node
689           self.op.src_path = src_path = utils.PathJoin(pathutils.EXPORT_DIR,
690                                                        src_path)
691           break
692       if not found:
693         raise errors.OpPrereqError("No export found for relative path %s" %
694                                    src_path, errors.ECODE_INVAL)
695
696     CheckNodeOnline(self, src_node)
697     result = self.rpc.call_export_info(src_node, src_path)
698     result.Raise("No export or invalid export found in dir %s" % src_path)
699
700     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
701     if not export_info.has_section(constants.INISECT_EXP):
702       raise errors.ProgrammerError("Corrupted export config",
703                                    errors.ECODE_ENVIRON)
704
705     ei_version = export_info.get(constants.INISECT_EXP, "version")
706     if (int(ei_version) != constants.EXPORT_VERSION):
707       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
708                                  (ei_version, constants.EXPORT_VERSION),
709                                  errors.ECODE_ENVIRON)
710     return export_info
711
712   def _ReadExportParams(self, einfo):
713     """Use export parameters as defaults.
714
715     In case the opcode doesn't specify (as in override) some instance
716     parameters, then try to use them from the export information, if
717     that declares them.
718
719     """
720     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
721
722     if self.op.disk_template is None:
723       if einfo.has_option(constants.INISECT_INS, "disk_template"):
724         self.op.disk_template = einfo.get(constants.INISECT_INS,
725                                           "disk_template")
726         if self.op.disk_template not in constants.DISK_TEMPLATES:
727           raise errors.OpPrereqError("Disk template specified in configuration"
728                                      " file is not one of the allowed values:"
729                                      " %s" %
730                                      " ".join(constants.DISK_TEMPLATES),
731                                      errors.ECODE_INVAL)
732       else:
733         raise errors.OpPrereqError("No disk template specified and the export"
734                                    " is missing the disk_template information",
735                                    errors.ECODE_INVAL)
736
737     if not self.op.disks:
738       disks = []
739       # TODO: import the disk iv_name too
740       for idx in range(constants.MAX_DISKS):
741         if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
742           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
743           disks.append({constants.IDISK_SIZE: disk_sz})
744       self.op.disks = disks
745       if not disks and self.op.disk_template != constants.DT_DISKLESS:
746         raise errors.OpPrereqError("No disk info specified and the export"
747                                    " is missing the disk information",
748                                    errors.ECODE_INVAL)
749
750     if not self.op.nics:
751       nics = []
752       for idx in range(constants.MAX_NICS):
753         if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
754           ndict = {}
755           for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
756             v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
757             ndict[name] = v
758           nics.append(ndict)
759         else:
760           break
761       self.op.nics = nics
762
763     if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
764       self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
765
766     if (self.op.hypervisor is None and
767         einfo.has_option(constants.INISECT_INS, "hypervisor")):
768       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
769
770     if einfo.has_section(constants.INISECT_HYP):
771       # use the export parameters but do not override the ones
772       # specified by the user
773       for name, value in einfo.items(constants.INISECT_HYP):
774         if name not in self.op.hvparams:
775           self.op.hvparams[name] = value
776
777     if einfo.has_section(constants.INISECT_BEP):
778       # use the parameters, without overriding
779       for name, value in einfo.items(constants.INISECT_BEP):
780         if name not in self.op.beparams:
781           self.op.beparams[name] = value
782         # Compatibility for the old "memory" be param
783         if name == constants.BE_MEMORY:
784           if constants.BE_MAXMEM not in self.op.beparams:
785             self.op.beparams[constants.BE_MAXMEM] = value
786           if constants.BE_MINMEM not in self.op.beparams:
787             self.op.beparams[constants.BE_MINMEM] = value
788     else:
789       # try to read the parameters old style, from the main section
790       for name in constants.BES_PARAMETERS:
791         if (name not in self.op.beparams and
792             einfo.has_option(constants.INISECT_INS, name)):
793           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
794
795     if einfo.has_section(constants.INISECT_OSP):
796       # use the parameters, without overriding
797       for name, value in einfo.items(constants.INISECT_OSP):
798         if name not in self.op.osparams:
799           self.op.osparams[name] = value
800
801   def _RevertToDefaults(self, cluster):
802     """Revert the instance parameters to the default values.
803
804     """
805     # hvparams
806     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
807     for name in self.op.hvparams.keys():
808       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
809         del self.op.hvparams[name]
810     # beparams
811     be_defs = cluster.SimpleFillBE({})
812     for name in self.op.beparams.keys():
813       if name in be_defs and be_defs[name] == self.op.beparams[name]:
814         del self.op.beparams[name]
815     # nic params
816     nic_defs = cluster.SimpleFillNIC({})
817     for nic in self.op.nics:
818       for name in constants.NICS_PARAMETERS:
819         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
820           del nic[name]
821     # osparams
822     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
823     for name in self.op.osparams.keys():
824       if name in os_defs and os_defs[name] == self.op.osparams[name]:
825         del self.op.osparams[name]
826
827   def _CalculateFileStorageDir(self):
828     """Calculate final instance file storage dir.
829
830     """
831     # file storage dir calculation/check
832     self.instance_file_storage_dir = None
833     if self.op.disk_template in constants.DTS_FILEBASED:
834       # build the full file storage dir path
835       joinargs = []
836
837       if self.op.disk_template == constants.DT_SHARED_FILE:
838         get_fsd_fn = self.cfg.GetSharedFileStorageDir
839       else:
840         get_fsd_fn = self.cfg.GetFileStorageDir
841
842       cfg_storagedir = get_fsd_fn()
843       if not cfg_storagedir:
844         raise errors.OpPrereqError("Cluster file storage dir not defined",
845                                    errors.ECODE_STATE)
846       joinargs.append(cfg_storagedir)
847
848       if self.op.file_storage_dir is not None:
849         joinargs.append(self.op.file_storage_dir)
850
851       joinargs.append(self.op.instance_name)
852
853       # pylint: disable=W0142
854       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
855
856   def CheckPrereq(self): # pylint: disable=R0914
857     """Check prerequisites.
858
859     """
860     # Check that the optimistically acquired groups are correct wrt the
861     # acquired nodes
862     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
863     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
864     cur_groups = list(self.cfg.GetNodeGroupsFromNodes(owned_nodes))
865     if not owned_groups.issuperset(cur_groups):
866       raise errors.OpPrereqError("New instance %s's node groups changed since"
867                                  " locks were acquired, current groups are"
868                                  " are '%s', owning groups '%s'; retry the"
869                                  " operation" %
870                                  (self.op.instance_name,
871                                   utils.CommaJoin(cur_groups),
872                                   utils.CommaJoin(owned_groups)),
873                                  errors.ECODE_STATE)
874
875     self._CalculateFileStorageDir()
876
877     if self.op.mode == constants.INSTANCE_IMPORT:
878       export_info = self._ReadExportInfo()
879       self._ReadExportParams(export_info)
880       self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
881     else:
882       self._old_instance_name = None
883
884     if (not self.cfg.GetVGName() and
885         self.op.disk_template not in constants.DTS_NOT_LVM):
886       raise errors.OpPrereqError("Cluster does not support lvm-based"
887                                  " instances", errors.ECODE_STATE)
888
889     if (self.op.hypervisor is None or
890         self.op.hypervisor == constants.VALUE_AUTO):
891       self.op.hypervisor = self.cfg.GetHypervisorType()
892
893     cluster = self.cfg.GetClusterInfo()
894     enabled_hvs = cluster.enabled_hypervisors
895     if self.op.hypervisor not in enabled_hvs:
896       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
897                                  " cluster (%s)" %
898                                  (self.op.hypervisor, ",".join(enabled_hvs)),
899                                  errors.ECODE_STATE)
900
901     # Check tag validity
902     for tag in self.op.tags:
903       objects.TaggableObject.ValidateTag(tag)
904
905     # check hypervisor parameter syntax (locally)
906     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
907     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
908                                       self.op.hvparams)
909     hv_type = hypervisor.GetHypervisorClass(self.op.hypervisor)
910     hv_type.CheckParameterSyntax(filled_hvp)
911     self.hv_full = filled_hvp
912     # check that we don't specify global parameters on an instance
913     CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, "hypervisor",
914                          "instance", "cluster")
915
916     # fill and remember the beparams dict
917     self.be_full = _ComputeFullBeParams(self.op, cluster)
918
919     # build os parameters
920     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
921
922     # now that hvp/bep are in final format, let's reset to defaults,
923     # if told to do so
924     if self.op.identify_defaults:
925       self._RevertToDefaults(cluster)
926
927     # NIC buildup
928     self.nics = _ComputeNics(self.op, cluster, self.check_ip, self.cfg,
929                              self.proc.GetECId())
930
931     # disk checks/pre-build
932     default_vg = self.cfg.GetVGName()
933     self.disks = ComputeDisks(self.op, default_vg)
934
935     if self.op.mode == constants.INSTANCE_IMPORT:
936       disk_images = []
937       for idx in range(len(self.disks)):
938         option = "disk%d_dump" % idx
939         if export_info.has_option(constants.INISECT_INS, option):
940           # FIXME: are the old os-es, disk sizes, etc. useful?
941           export_name = export_info.get(constants.INISECT_INS, option)
942           image = utils.PathJoin(self.op.src_path, export_name)
943           disk_images.append(image)
944         else:
945           disk_images.append(False)
946
947       self.src_images = disk_images
948
949       if self.op.instance_name == self._old_instance_name:
950         for idx, nic in enumerate(self.nics):
951           if nic.mac == constants.VALUE_AUTO:
952             nic_mac_ini = "nic%d_mac" % idx
953             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
954
955     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
956
957     # ip ping checks (we use the same ip that was resolved in ExpandNames)
958     if self.op.ip_check:
959       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
960         raise errors.OpPrereqError("IP %s of instance %s already in use" %
961                                    (self.check_ip, self.op.instance_name),
962                                    errors.ECODE_NOTUNIQUE)
963
964     #### mac address generation
965     # By generating here the mac address both the allocator and the hooks get
966     # the real final mac address rather than the 'auto' or 'generate' value.
967     # There is a race condition between the generation and the instance object
968     # creation, which means that we know the mac is valid now, but we're not
969     # sure it will be when we actually add the instance. If things go bad
970     # adding the instance will abort because of a duplicate mac, and the
971     # creation job will fail.
972     for nic in self.nics:
973       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
974         nic.mac = self.cfg.GenerateMAC(nic.network, self.proc.GetECId())
975
976     #### allocator run
977
978     if self.op.iallocator is not None:
979       self._RunAllocator()
980
981     # Release all unneeded node locks
982     keep_locks = filter(None, [self.op.pnode, self.op.snode, self.op.src_node])
983     ReleaseLocks(self, locking.LEVEL_NODE, keep=keep_locks)
984     ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=keep_locks)
985     ReleaseLocks(self, locking.LEVEL_NODE_ALLOC)
986     # Release all unneeded group locks
987     ReleaseLocks(self, locking.LEVEL_NODEGROUP,
988                  keep=self.cfg.GetNodeGroupsFromNodes(keep_locks))
989
990     assert (self.owned_locks(locking.LEVEL_NODE) ==
991             self.owned_locks(locking.LEVEL_NODE_RES)), \
992       "Node locks differ from node resource locks"
993
994     #### node related checks
995
996     # check primary node
997     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
998     assert self.pnode is not None, \
999       "Cannot retrieve locked node %s" % self.op.pnode
1000     if pnode.offline:
1001       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
1002                                  pnode.name, errors.ECODE_STATE)
1003     if pnode.drained:
1004       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
1005                                  pnode.name, errors.ECODE_STATE)
1006     if not pnode.vm_capable:
1007       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
1008                                  " '%s'" % pnode.name, errors.ECODE_STATE)
1009
1010     self.secondaries = []
1011
1012     # Fill in any IPs from IP pools. This must happen here, because we need to
1013     # know the nic's primary node, as specified by the iallocator
1014     for idx, nic in enumerate(self.nics):
1015       net_uuid = nic.network
1016       if net_uuid is not None:
1017         nobj = self.cfg.GetNetwork(net_uuid)
1018         netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.name)
1019         if netparams is None:
1020           raise errors.OpPrereqError("No netparams found for network"
1021                                      " %s. Propably not connected to"
1022                                      " node's %s nodegroup" %
1023                                      (nobj.name, self.pnode.name),
1024                                      errors.ECODE_INVAL)
1025         self.LogInfo("NIC/%d inherits netparams %s" %
1026                      (idx, netparams.values()))
1027         nic.nicparams = dict(netparams)
1028         if nic.ip is not None:
1029           if nic.ip.lower() == constants.NIC_IP_POOL:
1030             try:
1031               nic.ip = self.cfg.GenerateIp(net_uuid, self.proc.GetECId())
1032             except errors.ReservationError:
1033               raise errors.OpPrereqError("Unable to get a free IP for NIC %d"
1034                                          " from the address pool" % idx,
1035                                          errors.ECODE_STATE)
1036             self.LogInfo("Chose IP %s from network %s", nic.ip, nobj.name)
1037           else:
1038             try:
1039               self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId(),
1040                                  check=self.op.conflicts_check)
1041             except errors.ReservationError:
1042               raise errors.OpPrereqError("IP address %s already in use"
1043                                          " or does not belong to network %s" %
1044                                          (nic.ip, nobj.name),
1045                                          errors.ECODE_NOTUNIQUE)
1046
1047       # net is None, ip None or given
1048       elif self.op.conflicts_check:
1049         _CheckForConflictingIp(self, nic.ip, self.pnode.name)
1050
1051     # mirror node verification
1052     if self.op.disk_template in constants.DTS_INT_MIRROR:
1053       if self.op.snode == pnode.name:
1054         raise errors.OpPrereqError("The secondary node cannot be the"
1055                                    " primary node", errors.ECODE_INVAL)
1056       CheckNodeOnline(self, self.op.snode)
1057       CheckNodeNotDrained(self, self.op.snode)
1058       CheckNodeVmCapable(self, self.op.snode)
1059       self.secondaries.append(self.op.snode)
1060
1061       snode = self.cfg.GetNodeInfo(self.op.snode)
1062       if pnode.group != snode.group:
1063         self.LogWarning("The primary and secondary nodes are in two"
1064                         " different node groups; the disk parameters"
1065                         " from the first disk's node group will be"
1066                         " used")
1067
1068     if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
1069       nodes = [pnode]
1070       if self.op.disk_template in constants.DTS_INT_MIRROR:
1071         nodes.append(snode)
1072       has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
1073       if compat.any(map(has_es, nodes)):
1074         raise errors.OpPrereqError("Disk template %s not supported with"
1075                                    " exclusive storage" % self.op.disk_template,
1076                                    errors.ECODE_STATE)
1077
1078     nodenames = [pnode.name] + self.secondaries
1079
1080     if not self.adopt_disks:
1081       if self.op.disk_template == constants.DT_RBD:
1082         # _CheckRADOSFreeSpace() is just a placeholder.
1083         # Any function that checks prerequisites can be placed here.
1084         # Check if there is enough space on the RADOS cluster.
1085         CheckRADOSFreeSpace()
1086       elif self.op.disk_template == constants.DT_EXT:
1087         # FIXME: Function that checks prereqs if needed
1088         pass
1089       else:
1090         # Check lv size requirements, if not adopting
1091         req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
1092         CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
1093
1094     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
1095       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
1096                                 disk[constants.IDISK_ADOPT])
1097                      for disk in self.disks])
1098       if len(all_lvs) != len(self.disks):
1099         raise errors.OpPrereqError("Duplicate volume names given for adoption",
1100                                    errors.ECODE_INVAL)
1101       for lv_name in all_lvs:
1102         try:
1103           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
1104           # to ReserveLV uses the same syntax
1105           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
1106         except errors.ReservationError:
1107           raise errors.OpPrereqError("LV named %s used by another instance" %
1108                                      lv_name, errors.ECODE_NOTUNIQUE)
1109
1110       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
1111       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
1112
1113       node_lvs = self.rpc.call_lv_list([pnode.name],
1114                                        vg_names.payload.keys())[pnode.name]
1115       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
1116       node_lvs = node_lvs.payload
1117
1118       delta = all_lvs.difference(node_lvs.keys())
1119       if delta:
1120         raise errors.OpPrereqError("Missing logical volume(s): %s" %
1121                                    utils.CommaJoin(delta),
1122                                    errors.ECODE_INVAL)
1123       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
1124       if online_lvs:
1125         raise errors.OpPrereqError("Online logical volumes found, cannot"
1126                                    " adopt: %s" % utils.CommaJoin(online_lvs),
1127                                    errors.ECODE_STATE)
1128       # update the size of disk based on what is found
1129       for dsk in self.disks:
1130         dsk[constants.IDISK_SIZE] = \
1131           int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
1132                                         dsk[constants.IDISK_ADOPT])][0]))
1133
1134     elif self.op.disk_template == constants.DT_BLOCK:
1135       # Normalize and de-duplicate device paths
1136       all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
1137                        for disk in self.disks])
1138       if len(all_disks) != len(self.disks):
1139         raise errors.OpPrereqError("Duplicate disk names given for adoption",
1140                                    errors.ECODE_INVAL)
1141       baddisks = [d for d in all_disks
1142                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
1143       if baddisks:
1144         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
1145                                    " cannot be adopted" %
1146                                    (utils.CommaJoin(baddisks),
1147                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
1148                                    errors.ECODE_INVAL)
1149
1150       node_disks = self.rpc.call_bdev_sizes([pnode.name],
1151                                             list(all_disks))[pnode.name]
1152       node_disks.Raise("Cannot get block device information from node %s" %
1153                        pnode.name)
1154       node_disks = node_disks.payload
1155       delta = all_disks.difference(node_disks.keys())
1156       if delta:
1157         raise errors.OpPrereqError("Missing block device(s): %s" %
1158                                    utils.CommaJoin(delta),
1159                                    errors.ECODE_INVAL)
1160       for dsk in self.disks:
1161         dsk[constants.IDISK_SIZE] = \
1162           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
1163
1164     # Verify instance specs
1165     spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
1166     ispec = {
1167       constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
1168       constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
1169       constants.ISPEC_DISK_COUNT: len(self.disks),
1170       constants.ISPEC_DISK_SIZE: [disk[constants.IDISK_SIZE]
1171                                   for disk in self.disks],
1172       constants.ISPEC_NIC_COUNT: len(self.nics),
1173       constants.ISPEC_SPINDLE_USE: spindle_use,
1174       }
1175
1176     group_info = self.cfg.GetNodeGroup(pnode.group)
1177     ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1178     res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec,
1179                                                self.op.disk_template)
1180     if not self.op.ignore_ipolicy and res:
1181       msg = ("Instance allocation to group %s (%s) violates policy: %s" %
1182              (pnode.group, group_info.name, utils.CommaJoin(res)))
1183       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1184
1185     CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
1186
1187     CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
1188     # check OS parameters (remotely)
1189     CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
1190
1191     CheckNicsBridgesExist(self, self.nics, self.pnode.name)
1192
1193     #TODO: _CheckExtParams (remotely)
1194     # Check parameters for extstorage
1195
1196     # memory check on primary node
1197     #TODO(dynmem): use MINMEM for checking
1198     if self.op.start:
1199       CheckNodeFreeMemory(self, self.pnode.name,
1200                           "creating instance %s" % self.op.instance_name,
1201                           self.be_full[constants.BE_MAXMEM],
1202                           self.op.hypervisor)
1203
1204     self.dry_run_result = list(nodenames)
1205
1206   def Exec(self, feedback_fn):
1207     """Create and add the instance to the cluster.
1208
1209     """
1210     instance = self.op.instance_name
1211     pnode_name = self.pnode.name
1212
1213     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
1214                 self.owned_locks(locking.LEVEL_NODE)), \
1215       "Node locks differ from node resource locks"
1216     assert not self.glm.is_owned(locking.LEVEL_NODE_ALLOC)
1217
1218     ht_kind = self.op.hypervisor
1219     if ht_kind in constants.HTS_REQ_PORT:
1220       network_port = self.cfg.AllocatePort()
1221     else:
1222       network_port = None
1223
1224     # This is ugly but we got a chicken-egg problem here
1225     # We can only take the group disk parameters, as the instance
1226     # has no disks yet (we are generating them right here).
1227     node = self.cfg.GetNodeInfo(pnode_name)
1228     nodegroup = self.cfg.GetNodeGroup(node.group)
1229     disks = GenerateDiskTemplate(self,
1230                                  self.op.disk_template,
1231                                  instance, pnode_name,
1232                                  self.secondaries,
1233                                  self.disks,
1234                                  self.instance_file_storage_dir,
1235                                  self.op.file_driver,
1236                                  0,
1237                                  feedback_fn,
1238                                  self.cfg.GetGroupDiskParams(nodegroup))
1239
1240     iobj = objects.Instance(name=instance, os=self.op.os_type,
1241                             primary_node=pnode_name,
1242                             nics=self.nics, disks=disks,
1243                             disk_template=self.op.disk_template,
1244                             disks_active=False,
1245                             admin_state=constants.ADMINST_DOWN,
1246                             network_port=network_port,
1247                             beparams=self.op.beparams,
1248                             hvparams=self.op.hvparams,
1249                             hypervisor=self.op.hypervisor,
1250                             osparams=self.op.osparams,
1251                             )
1252
1253     if self.op.tags:
1254       for tag in self.op.tags:
1255         iobj.AddTag(tag)
1256
1257     if self.adopt_disks:
1258       if self.op.disk_template == constants.DT_PLAIN:
1259         # rename LVs to the newly-generated names; we need to construct
1260         # 'fake' LV disks with the old data, plus the new unique_id
1261         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
1262         rename_to = []
1263         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
1264           rename_to.append(t_dsk.logical_id)
1265           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
1266           self.cfg.SetDiskID(t_dsk, pnode_name)
1267         result = self.rpc.call_blockdev_rename(pnode_name,
1268                                                zip(tmp_disks, rename_to))
1269         result.Raise("Failed to rename adoped LVs")
1270     else:
1271       feedback_fn("* creating instance disks...")
1272       try:
1273         CreateDisks(self, iobj)
1274       except errors.OpExecError:
1275         self.LogWarning("Device creation failed")
1276         self.cfg.ReleaseDRBDMinors(instance)
1277         raise
1278
1279     feedback_fn("adding instance %s to cluster config" % instance)
1280
1281     self.cfg.AddInstance(iobj, self.proc.GetECId())
1282
1283     # Declare that we don't want to remove the instance lock anymore, as we've
1284     # added the instance to the config
1285     del self.remove_locks[locking.LEVEL_INSTANCE]
1286
1287     if self.op.mode == constants.INSTANCE_IMPORT:
1288       # Release unused nodes
1289       ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
1290     else:
1291       # Release all nodes
1292       ReleaseLocks(self, locking.LEVEL_NODE)
1293
1294     disk_abort = False
1295     if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
1296       feedback_fn("* wiping instance disks...")
1297       try:
1298         WipeDisks(self, iobj)
1299       except errors.OpExecError, err:
1300         logging.exception("Wiping disks failed")
1301         self.LogWarning("Wiping instance disks failed (%s)", err)
1302         disk_abort = True
1303
1304     if disk_abort:
1305       # Something is already wrong with the disks, don't do anything else
1306       pass
1307     elif self.op.wait_for_sync:
1308       disk_abort = not WaitForSync(self, iobj)
1309     elif iobj.disk_template in constants.DTS_INT_MIRROR:
1310       # make sure the disks are not degraded (still sync-ing is ok)
1311       feedback_fn("* checking mirrors status")
1312       disk_abort = not WaitForSync(self, iobj, oneshot=True)
1313     else:
1314       disk_abort = False
1315
1316     if disk_abort:
1317       RemoveDisks(self, iobj)
1318       self.cfg.RemoveInstance(iobj.name)
1319       # Make sure the instance lock gets removed
1320       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
1321       raise errors.OpExecError("There are some degraded disks for"
1322                                " this instance")
1323
1324     # instance disks are now active
1325     iobj.disks_active = True
1326
1327     # Release all node resource locks
1328     ReleaseLocks(self, locking.LEVEL_NODE_RES)
1329
1330     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
1331       # we need to set the disks ID to the primary node, since the
1332       # preceding code might or might have not done it, depending on
1333       # disk template and other options
1334       for disk in iobj.disks:
1335         self.cfg.SetDiskID(disk, pnode_name)
1336       if self.op.mode == constants.INSTANCE_CREATE:
1337         if not self.op.no_install:
1338           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
1339                         not self.op.wait_for_sync)
1340           if pause_sync:
1341             feedback_fn("* pausing disk sync to install instance OS")
1342             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1343                                                               (iobj.disks,
1344                                                                iobj), True)
1345             for idx, success in enumerate(result.payload):
1346               if not success:
1347                 logging.warn("pause-sync of instance %s for disk %d failed",
1348                              instance, idx)
1349
1350           feedback_fn("* running the instance OS create scripts...")
1351           # FIXME: pass debug option from opcode to backend
1352           os_add_result = \
1353             self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
1354                                           self.op.debug_level)
1355           if pause_sync:
1356             feedback_fn("* resuming disk sync")
1357             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1358                                                               (iobj.disks,
1359                                                                iobj), False)
1360             for idx, success in enumerate(result.payload):
1361               if not success:
1362                 logging.warn("resume-sync of instance %s for disk %d failed",
1363                              instance, idx)
1364
1365           os_add_result.Raise("Could not add os for instance %s"
1366                               " on node %s" % (instance, pnode_name))
1367
1368       else:
1369         if self.op.mode == constants.INSTANCE_IMPORT:
1370           feedback_fn("* running the instance OS import scripts...")
1371
1372           transfers = []
1373
1374           for idx, image in enumerate(self.src_images):
1375             if not image:
1376               continue
1377
1378             # FIXME: pass debug option from opcode to backend
1379             dt = masterd.instance.DiskTransfer("disk/%s" % idx,
1380                                                constants.IEIO_FILE, (image, ),
1381                                                constants.IEIO_SCRIPT,
1382                                                (iobj.disks[idx], idx),
1383                                                None)
1384             transfers.append(dt)
1385
1386           import_result = \
1387             masterd.instance.TransferInstanceData(self, feedback_fn,
1388                                                   self.op.src_node, pnode_name,
1389                                                   self.pnode.secondary_ip,
1390                                                   iobj, transfers)
1391           if not compat.all(import_result):
1392             self.LogWarning("Some disks for instance %s on node %s were not"
1393                             " imported successfully" % (instance, pnode_name))
1394
1395           rename_from = self._old_instance_name
1396
1397         elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
1398           feedback_fn("* preparing remote import...")
1399           # The source cluster will stop the instance before attempting to make
1400           # a connection. In some cases stopping an instance can take a long
1401           # time, hence the shutdown timeout is added to the connection
1402           # timeout.
1403           connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
1404                              self.op.source_shutdown_timeout)
1405           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
1406
1407           assert iobj.primary_node == self.pnode.name
1408           disk_results = \
1409             masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
1410                                           self.source_x509_ca,
1411                                           self._cds, timeouts)
1412           if not compat.all(disk_results):
1413             # TODO: Should the instance still be started, even if some disks
1414             # failed to import (valid for local imports, too)?
1415             self.LogWarning("Some disks for instance %s on node %s were not"
1416                             " imported successfully" % (instance, pnode_name))
1417
1418           rename_from = self.source_instance_name
1419
1420         else:
1421           # also checked in the prereq part
1422           raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
1423                                        % self.op.mode)
1424
1425         # Run rename script on newly imported instance
1426         assert iobj.name == instance
1427         feedback_fn("Running rename script for %s" % instance)
1428         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
1429                                                    rename_from,
1430                                                    self.op.debug_level)
1431         if result.fail_msg:
1432           self.LogWarning("Failed to run rename script for %s on node"
1433                           " %s: %s" % (instance, pnode_name, result.fail_msg))
1434
1435     assert not self.owned_locks(locking.LEVEL_NODE_RES)
1436
1437     if self.op.start:
1438       iobj.admin_state = constants.ADMINST_UP
1439       self.cfg.Update(iobj, feedback_fn)
1440       logging.info("Starting instance %s on node %s", instance, pnode_name)
1441       feedback_fn("* starting instance...")
1442       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
1443                                             False, self.op.reason)
1444       result.Raise("Could not start instance")
1445
1446     return list(iobj.all_nodes)
1447
1448
1449 class LUInstanceRename(LogicalUnit):
1450   """Rename an instance.
1451
1452   """
1453   HPATH = "instance-rename"
1454   HTYPE = constants.HTYPE_INSTANCE
1455
1456   def CheckArguments(self):
1457     """Check arguments.
1458
1459     """
1460     if self.op.ip_check and not self.op.name_check:
1461       # TODO: make the ip check more flexible and not depend on the name check
1462       raise errors.OpPrereqError("IP address check requires a name check",
1463                                  errors.ECODE_INVAL)
1464
1465   def BuildHooksEnv(self):
1466     """Build hooks env.
1467
1468     This runs on master, primary and secondary nodes of the instance.
1469
1470     """
1471     env = BuildInstanceHookEnvByObject(self, self.instance)
1472     env["INSTANCE_NEW_NAME"] = self.op.new_name
1473     return env
1474
1475   def BuildHooksNodes(self):
1476     """Build hooks nodes.
1477
1478     """
1479     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
1480     return (nl, nl)
1481
1482   def CheckPrereq(self):
1483     """Check prerequisites.
1484
1485     This checks that the instance is in the cluster and is not running.
1486
1487     """
1488     self.op.instance_name = ExpandInstanceName(self.cfg,
1489                                                self.op.instance_name)
1490     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1491     assert instance is not None
1492     CheckNodeOnline(self, instance.primary_node)
1493     CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
1494                        msg="cannot rename")
1495     self.instance = instance
1496
1497     new_name = self.op.new_name
1498     if self.op.name_check:
1499       hostname = _CheckHostnameSane(self, new_name)
1500       new_name = self.op.new_name = hostname.name
1501       if (self.op.ip_check and
1502           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
1503         raise errors.OpPrereqError("IP %s of instance %s already in use" %
1504                                    (hostname.ip, new_name),
1505                                    errors.ECODE_NOTUNIQUE)
1506
1507     instance_list = self.cfg.GetInstanceList()
1508     if new_name in instance_list and new_name != instance.name:
1509       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
1510                                  new_name, errors.ECODE_EXISTS)
1511
1512   def Exec(self, feedback_fn):
1513     """Rename the instance.
1514
1515     """
1516     inst = self.instance
1517     old_name = inst.name
1518
1519     rename_file_storage = False
1520     if (inst.disk_template in constants.DTS_FILEBASED and
1521         self.op.new_name != inst.name):
1522       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1523       rename_file_storage = True
1524
1525     self.cfg.RenameInstance(inst.name, self.op.new_name)
1526     # Change the instance lock. This is definitely safe while we hold the BGL.
1527     # Otherwise the new lock would have to be added in acquired mode.
1528     assert self.REQ_BGL
1529     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER)
1530     self.glm.remove(locking.LEVEL_INSTANCE, old_name)
1531     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
1532
1533     # re-read the instance from the configuration after rename
1534     inst = self.cfg.GetInstanceInfo(self.op.new_name)
1535
1536     if rename_file_storage:
1537       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1538       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
1539                                                      old_file_storage_dir,
1540                                                      new_file_storage_dir)
1541       result.Raise("Could not rename on node %s directory '%s' to '%s'"
1542                    " (but the instance has been renamed in Ganeti)" %
1543                    (inst.primary_node, old_file_storage_dir,
1544                     new_file_storage_dir))
1545
1546     StartInstanceDisks(self, inst, None)
1547     # update info on disks
1548     info = GetInstanceInfoText(inst)
1549     for (idx, disk) in enumerate(inst.disks):
1550       for node in inst.all_nodes:
1551         self.cfg.SetDiskID(disk, node)
1552         result = self.rpc.call_blockdev_setinfo(node, disk, info)
1553         if result.fail_msg:
1554           self.LogWarning("Error setting info on node %s for disk %s: %s",
1555                           node, idx, result.fail_msg)
1556     try:
1557       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
1558                                                  old_name, self.op.debug_level)
1559       msg = result.fail_msg
1560       if msg:
1561         msg = ("Could not run OS rename script for instance %s on node %s"
1562                " (but the instance has been renamed in Ganeti): %s" %
1563                (inst.name, inst.primary_node, msg))
1564         self.LogWarning(msg)
1565     finally:
1566       ShutdownInstanceDisks(self, inst)
1567
1568     return inst.name
1569
1570
1571 class LUInstanceRemove(LogicalUnit):
1572   """Remove an instance.
1573
1574   """
1575   HPATH = "instance-remove"
1576   HTYPE = constants.HTYPE_INSTANCE
1577   REQ_BGL = False
1578
1579   def ExpandNames(self):
1580     self._ExpandAndLockInstance()
1581     self.needed_locks[locking.LEVEL_NODE] = []
1582     self.needed_locks[locking.LEVEL_NODE_RES] = []
1583     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
1584
1585   def DeclareLocks(self, level):
1586     if level == locking.LEVEL_NODE:
1587       self._LockInstancesNodes()
1588     elif level == locking.LEVEL_NODE_RES:
1589       # Copy node locks
1590       self.needed_locks[locking.LEVEL_NODE_RES] = \
1591         CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1592
1593   def BuildHooksEnv(self):
1594     """Build hooks env.
1595
1596     This runs on master, primary and secondary nodes of the instance.
1597
1598     """
1599     env = BuildInstanceHookEnvByObject(self, self.instance)
1600     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
1601     return env
1602
1603   def BuildHooksNodes(self):
1604     """Build hooks nodes.
1605
1606     """
1607     nl = [self.cfg.GetMasterNode()]
1608     nl_post = list(self.instance.all_nodes) + nl
1609     return (nl, nl_post)
1610
1611   def CheckPrereq(self):
1612     """Check prerequisites.
1613
1614     This checks that the instance is in the cluster.
1615
1616     """
1617     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1618     assert self.instance is not None, \
1619       "Cannot retrieve locked instance %s" % self.op.instance_name
1620
1621   def Exec(self, feedback_fn):
1622     """Remove the instance.
1623
1624     """
1625     instance = self.instance
1626     logging.info("Shutting down instance %s on node %s",
1627                  instance.name, instance.primary_node)
1628
1629     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
1630                                              self.op.shutdown_timeout,
1631                                              self.op.reason)
1632     msg = result.fail_msg
1633     if msg:
1634       if self.op.ignore_failures:
1635         feedback_fn("Warning: can't shutdown instance: %s" % msg)
1636       else:
1637         raise errors.OpExecError("Could not shutdown instance %s on"
1638                                  " node %s: %s" %
1639                                  (instance.name, instance.primary_node, msg))
1640
1641     assert (self.owned_locks(locking.LEVEL_NODE) ==
1642             self.owned_locks(locking.LEVEL_NODE_RES))
1643     assert not (set(instance.all_nodes) -
1644                 self.owned_locks(locking.LEVEL_NODE)), \
1645       "Not owning correct locks"
1646
1647     RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
1648
1649
1650 class LUInstanceMove(LogicalUnit):
1651   """Move an instance by data-copying.
1652
1653   """
1654   HPATH = "instance-move"
1655   HTYPE = constants.HTYPE_INSTANCE
1656   REQ_BGL = False
1657
1658   def ExpandNames(self):
1659     self._ExpandAndLockInstance()
1660     target_node = ExpandNodeName(self.cfg, self.op.target_node)
1661     self.op.target_node = target_node
1662     self.needed_locks[locking.LEVEL_NODE] = [target_node]
1663     self.needed_locks[locking.LEVEL_NODE_RES] = []
1664     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
1665
1666   def DeclareLocks(self, level):
1667     if level == locking.LEVEL_NODE:
1668       self._LockInstancesNodes(primary_only=True)
1669     elif level == locking.LEVEL_NODE_RES:
1670       # Copy node locks
1671       self.needed_locks[locking.LEVEL_NODE_RES] = \
1672         CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1673
1674   def BuildHooksEnv(self):
1675     """Build hooks env.
1676
1677     This runs on master, primary and secondary nodes of the instance.
1678
1679     """
1680     env = {
1681       "TARGET_NODE": self.op.target_node,
1682       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
1683       }
1684     env.update(BuildInstanceHookEnvByObject(self, self.instance))
1685     return env
1686
1687   def BuildHooksNodes(self):
1688     """Build hooks nodes.
1689
1690     """
1691     nl = [
1692       self.cfg.GetMasterNode(),
1693       self.instance.primary_node,
1694       self.op.target_node,
1695       ]
1696     return (nl, nl)
1697
1698   def CheckPrereq(self):
1699     """Check prerequisites.
1700
1701     This checks that the instance is in the cluster.
1702
1703     """
1704     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1705     assert self.instance is not None, \
1706       "Cannot retrieve locked instance %s" % self.op.instance_name
1707
1708     if instance.disk_template not in constants.DTS_COPYABLE:
1709       raise errors.OpPrereqError("Disk template %s not suitable for copying" %
1710                                  instance.disk_template, errors.ECODE_STATE)
1711
1712     node = self.cfg.GetNodeInfo(self.op.target_node)
1713     assert node is not None, \
1714       "Cannot retrieve locked node %s" % self.op.target_node
1715
1716     self.target_node = target_node = node.name
1717
1718     if target_node == instance.primary_node:
1719       raise errors.OpPrereqError("Instance %s is already on the node %s" %
1720                                  (instance.name, target_node),
1721                                  errors.ECODE_STATE)
1722
1723     bep = self.cfg.GetClusterInfo().FillBE(instance)
1724
1725     for idx, dsk in enumerate(instance.disks):
1726       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
1727         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
1728                                    " cannot copy" % idx, errors.ECODE_STATE)
1729
1730     CheckNodeOnline(self, target_node)
1731     CheckNodeNotDrained(self, target_node)
1732     CheckNodeVmCapable(self, target_node)
1733     cluster = self.cfg.GetClusterInfo()
1734     group_info = self.cfg.GetNodeGroup(node.group)
1735     ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1736     CheckTargetNodeIPolicy(self, ipolicy, instance, node, self.cfg,
1737                            ignore=self.op.ignore_ipolicy)
1738
1739     if instance.admin_state == constants.ADMINST_UP:
1740       # check memory requirements on the secondary node
1741       CheckNodeFreeMemory(self, target_node,
1742                           "failing over instance %s" %
1743                           instance.name, bep[constants.BE_MAXMEM],
1744                           instance.hypervisor)
1745     else:
1746       self.LogInfo("Not checking memory on the secondary node as"
1747                    " instance will not be started")
1748
1749     # check bridge existance
1750     CheckInstanceBridgesExist(self, instance, node=target_node)
1751
1752   def Exec(self, feedback_fn):
1753     """Move an instance.
1754
1755     The move is done by shutting it down on its present node, copying
1756     the data over (slow) and starting it on the new node.
1757
1758     """
1759     instance = self.instance
1760
1761     source_node = instance.primary_node
1762     target_node = self.target_node
1763
1764     self.LogInfo("Shutting down instance %s on source node %s",
1765                  instance.name, source_node)
1766
1767     assert (self.owned_locks(locking.LEVEL_NODE) ==
1768             self.owned_locks(locking.LEVEL_NODE_RES))
1769
1770     result = self.rpc.call_instance_shutdown(source_node, instance,
1771                                              self.op.shutdown_timeout,
1772                                              self.op.reason)
1773     msg = result.fail_msg
1774     if msg:
1775       if self.op.ignore_consistency:
1776         self.LogWarning("Could not shutdown instance %s on node %s."
1777                         " Proceeding anyway. Please make sure node"
1778                         " %s is down. Error details: %s",
1779                         instance.name, source_node, source_node, msg)
1780       else:
1781         raise errors.OpExecError("Could not shutdown instance %s on"
1782                                  " node %s: %s" %
1783                                  (instance.name, source_node, msg))
1784
1785     # create the target disks
1786     try:
1787       CreateDisks(self, instance, target_node=target_node)
1788     except errors.OpExecError:
1789       self.LogWarning("Device creation failed")
1790       self.cfg.ReleaseDRBDMinors(instance.name)
1791       raise
1792
1793     cluster_name = self.cfg.GetClusterInfo().cluster_name
1794
1795     errs = []
1796     # activate, get path, copy the data over
1797     for idx, disk in enumerate(instance.disks):
1798       self.LogInfo("Copying data for disk %d", idx)
1799       result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
1800                                                instance.name, True, idx)
1801       if result.fail_msg:
1802         self.LogWarning("Can't assemble newly created disk %d: %s",
1803                         idx, result.fail_msg)
1804         errs.append(result.fail_msg)
1805         break
1806       dev_path, _ = result.payload
1807       result = self.rpc.call_blockdev_export(source_node, (disk, instance),
1808                                              target_node, dev_path,
1809                                              cluster_name)
1810       if result.fail_msg:
1811         self.LogWarning("Can't copy data over for disk %d: %s",
1812                         idx, result.fail_msg)
1813         errs.append(result.fail_msg)
1814         break
1815
1816     if errs:
1817       self.LogWarning("Some disks failed to copy, aborting")
1818       try:
1819         RemoveDisks(self, instance, target_node=target_node)
1820       finally:
1821         self.cfg.ReleaseDRBDMinors(instance.name)
1822         raise errors.OpExecError("Errors during disk copy: %s" %
1823                                  (",".join(errs),))
1824
1825     instance.primary_node = target_node
1826     self.cfg.Update(instance, feedback_fn)
1827
1828     self.LogInfo("Removing the disks on the original node")
1829     RemoveDisks(self, instance, target_node=source_node)
1830
1831     # Only start the instance if it's marked as up
1832     if instance.admin_state == constants.ADMINST_UP:
1833       self.LogInfo("Starting instance %s on node %s",
1834                    instance.name, target_node)
1835
1836       disks_ok, _ = AssembleInstanceDisks(self, instance,
1837                                           ignore_secondaries=True)
1838       if not disks_ok:
1839         ShutdownInstanceDisks(self, instance)
1840         raise errors.OpExecError("Can't activate the instance's disks")
1841
1842       result = self.rpc.call_instance_start(target_node,
1843                                             (instance, None, None), False,
1844                                             self.op.reason)
1845       msg = result.fail_msg
1846       if msg:
1847         ShutdownInstanceDisks(self, instance)
1848         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
1849                                  (instance.name, target_node, msg))
1850
1851
1852 class LUInstanceMultiAlloc(NoHooksLU):
1853   """Allocates multiple instances at the same time.
1854
1855   """
1856   REQ_BGL = False
1857
1858   def CheckArguments(self):
1859     """Check arguments.
1860
1861     """
1862     nodes = []
1863     for inst in self.op.instances:
1864       if inst.iallocator is not None:
1865         raise errors.OpPrereqError("iallocator are not allowed to be set on"
1866                                    " instance objects", errors.ECODE_INVAL)
1867       nodes.append(bool(inst.pnode))
1868       if inst.disk_template in constants.DTS_INT_MIRROR:
1869         nodes.append(bool(inst.snode))
1870
1871     has_nodes = compat.any(nodes)
1872     if compat.all(nodes) ^ has_nodes:
1873       raise errors.OpPrereqError("There are instance objects providing"
1874                                  " pnode/snode while others do not",
1875                                  errors.ECODE_INVAL)
1876
1877     if not has_nodes and self.op.iallocator is None:
1878       default_iallocator = self.cfg.GetDefaultIAllocator()
1879       if default_iallocator:
1880         self.op.iallocator = default_iallocator
1881       else:
1882         raise errors.OpPrereqError("No iallocator or nodes on the instances"
1883                                    " given and no cluster-wide default"
1884                                    " iallocator found; please specify either"
1885                                    " an iallocator or nodes on the instances"
1886                                    " or set a cluster-wide default iallocator",
1887                                    errors.ECODE_INVAL)
1888
1889     _CheckOpportunisticLocking(self.op)
1890
1891     dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
1892     if dups:
1893       raise errors.OpPrereqError("There are duplicate instance names: %s" %
1894                                  utils.CommaJoin(dups), errors.ECODE_INVAL)
1895
1896   def ExpandNames(self):
1897     """Calculate the locks.
1898
1899     """
1900     self.share_locks = ShareAll()
1901     self.needed_locks = {
1902       # iallocator will select nodes and even if no iallocator is used,
1903       # collisions with LUInstanceCreate should be avoided
1904       locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
1905       }
1906
1907     if self.op.iallocator:
1908       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
1909       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
1910
1911       if self.op.opportunistic_locking:
1912         self.opportunistic_locks[locking.LEVEL_NODE] = True
1913     else:
1914       nodeslist = []
1915       for inst in self.op.instances:
1916         inst.pnode = ExpandNodeName(self.cfg, inst.pnode)
1917         nodeslist.append(inst.pnode)
1918         if inst.snode is not None:
1919           inst.snode = ExpandNodeName(self.cfg, inst.snode)
1920           nodeslist.append(inst.snode)
1921
1922       self.needed_locks[locking.LEVEL_NODE] = nodeslist
1923       # Lock resources of instance's primary and secondary nodes (copy to
1924       # prevent accidential modification)
1925       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
1926
1927   def DeclareLocks(self, level):
1928     if level == locking.LEVEL_NODE_RES and \
1929       self.opportunistic_locks[locking.LEVEL_NODE]:
1930       # Even when using opportunistic locking, we require the same set of
1931       # NODE_RES locks as we got NODE locks
1932       self.needed_locks[locking.LEVEL_NODE_RES] = \
1933         self.owned_locks(locking.LEVEL_NODE)
1934
1935   def CheckPrereq(self):
1936     """Check prerequisite.
1937
1938     """
1939     if self.op.iallocator:
1940       cluster = self.cfg.GetClusterInfo()
1941       default_vg = self.cfg.GetVGName()
1942       ec_id = self.proc.GetECId()
1943
1944       if self.op.opportunistic_locking:
1945         # Only consider nodes for which a lock is held
1946         node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
1947       else:
1948         node_whitelist = None
1949
1950       insts = [_CreateInstanceAllocRequest(op, ComputeDisks(op, default_vg),
1951                                            _ComputeNics(op, cluster, None,
1952                                                         self.cfg, ec_id),
1953                                            _ComputeFullBeParams(op, cluster),
1954                                            node_whitelist)
1955                for op in self.op.instances]
1956
1957       req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
1958       ial = iallocator.IAllocator(self.cfg, self.rpc, req)
1959
1960       ial.Run(self.op.iallocator)
1961
1962       if not ial.success:
1963         raise errors.OpPrereqError("Can't compute nodes using"
1964                                    " iallocator '%s': %s" %
1965                                    (self.op.iallocator, ial.info),
1966                                    errors.ECODE_NORES)
1967
1968       self.ia_result = ial.result
1969
1970     if self.op.dry_run:
1971       self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
1972         constants.JOB_IDS_KEY: [],
1973         })
1974
1975   def _ConstructPartialResult(self):
1976     """Contructs the partial result.
1977
1978     """
1979     if self.op.iallocator:
1980       (allocatable, failed_insts) = self.ia_result
1981       allocatable_insts = map(compat.fst, allocatable)
1982     else:
1983       allocatable_insts = [op.instance_name for op in self.op.instances]
1984       failed_insts = []
1985
1986     return {
1987       opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY: allocatable_insts,
1988       opcodes.OpInstanceMultiAlloc.FAILED_KEY: failed_insts,
1989       }
1990
1991   def Exec(self, feedback_fn):
1992     """Executes the opcode.
1993
1994     """
1995     jobs = []
1996     if self.op.iallocator:
1997       op2inst = dict((op.instance_name, op) for op in self.op.instances)
1998       (allocatable, failed) = self.ia_result
1999
2000       for (name, nodes) in allocatable:
2001         op = op2inst.pop(name)
2002
2003         if len(nodes) > 1:
2004           (op.pnode, op.snode) = nodes
2005         else:
2006           (op.pnode,) = nodes
2007
2008         jobs.append([op])
2009
2010       missing = set(op2inst.keys()) - set(failed)
2011       assert not missing, \
2012         "Iallocator did return incomplete result: %s" % \
2013         utils.CommaJoin(missing)
2014     else:
2015       jobs.extend([op] for op in self.op.instances)
2016
2017     return ResultWithJobs(jobs, **self._ConstructPartialResult())
2018
2019
2020 class _InstNicModPrivate:
2021   """Data structure for network interface modifications.
2022
2023   Used by L{LUInstanceSetParams}.
2024
2025   """
2026   def __init__(self):
2027     self.params = None
2028     self.filled = None
2029
2030
2031 def _PrepareContainerMods(mods, private_fn):
2032   """Prepares a list of container modifications by adding a private data field.
2033
2034   @type mods: list of tuples; (operation, index, parameters)
2035   @param mods: List of modifications
2036   @type private_fn: callable or None
2037   @param private_fn: Callable for constructing a private data field for a
2038     modification
2039   @rtype: list
2040
2041   """
2042   if private_fn is None:
2043     fn = lambda: None
2044   else:
2045     fn = private_fn
2046
2047   return [(op, idx, params, fn()) for (op, idx, params) in mods]
2048
2049
2050 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
2051   """Checks if nodes have enough physical CPUs
2052
2053   This function checks if all given nodes have the needed number of
2054   physical CPUs. In case any node has less CPUs or we cannot get the
2055   information from the node, this function raises an OpPrereqError
2056   exception.
2057
2058   @type lu: C{LogicalUnit}
2059   @param lu: a logical unit from which we get configuration data
2060   @type nodenames: C{list}
2061   @param nodenames: the list of node names to check
2062   @type requested: C{int}
2063   @param requested: the minimum acceptable number of physical CPUs
2064   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
2065       or we cannot check the node
2066
2067   """
2068   nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name], None)
2069   for node in nodenames:
2070     info = nodeinfo[node]
2071     info.Raise("Cannot get current information from node %s" % node,
2072                prereq=True, ecode=errors.ECODE_ENVIRON)
2073     (_, _, (hv_info, )) = info.payload
2074     num_cpus = hv_info.get("cpu_total", None)
2075     if not isinstance(num_cpus, int):
2076       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
2077                                  " on node %s, result was '%s'" %
2078                                  (node, num_cpus), errors.ECODE_ENVIRON)
2079     if requested > num_cpus:
2080       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
2081                                  "required" % (node, num_cpus, requested),
2082                                  errors.ECODE_NORES)
2083
2084
2085 def GetItemFromContainer(identifier, kind, container):
2086   """Return the item refered by the identifier.
2087
2088   @type identifier: string
2089   @param identifier: Item index or name or UUID
2090   @type kind: string
2091   @param kind: One-word item description
2092   @type container: list
2093   @param container: Container to get the item from
2094
2095   """
2096   # Index
2097   try:
2098     idx = int(identifier)
2099     if idx == -1:
2100       # Append
2101       absidx = len(container) - 1
2102     elif idx < 0:
2103       raise IndexError("Not accepting negative indices other than -1")
2104     elif idx > len(container):
2105       raise IndexError("Got %s index %s, but there are only %s" %
2106                        (kind, idx, len(container)))
2107     else:
2108       absidx = idx
2109     return (absidx, container[idx])
2110   except ValueError:
2111     pass
2112
2113   for idx, item in enumerate(container):
2114     if item.uuid == identifier or item.name == identifier:
2115       return (idx, item)
2116
2117   raise errors.OpPrereqError("Cannot find %s with identifier %s" %
2118                              (kind, identifier), errors.ECODE_NOENT)
2119
2120
2121 def _ApplyContainerMods(kind, container, chgdesc, mods,
2122                         create_fn, modify_fn, remove_fn):
2123   """Applies descriptions in C{mods} to C{container}.
2124
2125   @type kind: string
2126   @param kind: One-word item description
2127   @type container: list
2128   @param container: Container to modify
2129   @type chgdesc: None or list
2130   @param chgdesc: List of applied changes
2131   @type mods: list
2132   @param mods: Modifications as returned by L{_PrepareContainerMods}
2133   @type create_fn: callable
2134   @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
2135     receives absolute item index, parameters and private data object as added
2136     by L{_PrepareContainerMods}, returns tuple containing new item and changes
2137     as list
2138   @type modify_fn: callable
2139   @param modify_fn: Callback for modifying an existing item
2140     (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
2141     and private data object as added by L{_PrepareContainerMods}, returns
2142     changes as list
2143   @type remove_fn: callable
2144   @param remove_fn: Callback on removing item; receives absolute item index,
2145     item and private data object as added by L{_PrepareContainerMods}
2146
2147   """
2148   for (op, identifier, params, private) in mods:
2149     changes = None
2150
2151     if op == constants.DDM_ADD:
2152       # Calculate where item will be added
2153       # When adding an item, identifier can only be an index
2154       try:
2155         idx = int(identifier)
2156       except ValueError:
2157         raise errors.OpPrereqError("Only possitive integer or -1 is accepted as"
2158                                    " identifier for %s" % constants.DDM_ADD,
2159                                    errors.ECODE_INVAL)
2160       if idx == -1:
2161         addidx = len(container)
2162       else:
2163         if idx < 0:
2164           raise IndexError("Not accepting negative indices other than -1")
2165         elif idx > len(container):
2166           raise IndexError("Got %s index %s, but there are only %s" %
2167                            (kind, idx, len(container)))
2168         addidx = idx
2169
2170       if create_fn is None:
2171         item = params
2172       else:
2173         (item, changes) = create_fn(addidx, params, private)
2174
2175       if idx == -1:
2176         container.append(item)
2177       else:
2178         assert idx >= 0
2179         assert idx <= len(container)
2180         # list.insert does so before the specified index
2181         container.insert(idx, item)
2182     else:
2183       # Retrieve existing item
2184       (absidx, item) = GetItemFromContainer(identifier, kind, container)
2185
2186       if op == constants.DDM_REMOVE:
2187         assert not params
2188
2189         changes = [("%s/%s" % (kind, absidx), "remove")]
2190
2191         if remove_fn is not None:
2192           msg = remove_fn(absidx, item, private)
2193           if msg:
2194             changes.append(("%s/%s" % (kind, absidx), msg))
2195
2196         assert container[absidx] == item
2197         del container[absidx]
2198       elif op == constants.DDM_MODIFY:
2199         if modify_fn is not None:
2200           changes = modify_fn(absidx, item, params, private)
2201       else:
2202         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2203
2204     assert _TApplyContModsCbChanges(changes)
2205
2206     if not (chgdesc is None or changes is None):
2207       chgdesc.extend(changes)
2208
2209
2210 def _UpdateIvNames(base_index, disks):
2211   """Updates the C{iv_name} attribute of disks.
2212
2213   @type disks: list of L{objects.Disk}
2214
2215   """
2216   for (idx, disk) in enumerate(disks):
2217     disk.iv_name = "disk/%s" % (base_index + idx, )
2218
2219
2220 class LUInstanceSetParams(LogicalUnit):
2221   """Modifies an instances's parameters.
2222
2223   """
2224   HPATH = "instance-modify"
2225   HTYPE = constants.HTYPE_INSTANCE
2226   REQ_BGL = False
2227
2228   @staticmethod
2229   def _UpgradeDiskNicMods(kind, mods, verify_fn):
2230     assert ht.TList(mods)
2231     assert not mods or len(mods[0]) in (2, 3)
2232
2233     if mods and len(mods[0]) == 2:
2234       result = []
2235
2236       addremove = 0
2237       for op, params in mods:
2238         if op in (constants.DDM_ADD, constants.DDM_REMOVE):
2239           result.append((op, -1, params))
2240           addremove += 1
2241
2242           if addremove > 1:
2243             raise errors.OpPrereqError("Only one %s add or remove operation is"
2244                                        " supported at a time" % kind,
2245                                        errors.ECODE_INVAL)
2246         else:
2247           result.append((constants.DDM_MODIFY, op, params))
2248
2249       assert verify_fn(result)
2250     else:
2251       result = mods
2252
2253     return result
2254
2255   @staticmethod
2256   def _CheckMods(kind, mods, key_types, item_fn):
2257     """Ensures requested disk/NIC modifications are valid.
2258
2259     """
2260     for (op, _, params) in mods:
2261       assert ht.TDict(params)
2262
2263       # If 'key_types' is an empty dict, we assume we have an
2264       # 'ext' template and thus do not ForceDictType
2265       if key_types:
2266         utils.ForceDictType(params, key_types)
2267
2268       if op == constants.DDM_REMOVE:
2269         if params:
2270           raise errors.OpPrereqError("No settings should be passed when"
2271                                      " removing a %s" % kind,
2272                                      errors.ECODE_INVAL)
2273       elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
2274         item_fn(op, params)
2275       else:
2276         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2277
2278   def _VerifyDiskModification(self, op, params):
2279     """Verifies a disk modification.
2280
2281     """
2282     if op == constants.DDM_ADD:
2283       mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
2284       if mode not in constants.DISK_ACCESS_SET:
2285         raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
2286                                    errors.ECODE_INVAL)
2287
2288       size = params.get(constants.IDISK_SIZE, None)
2289       if size is None:
2290         raise errors.OpPrereqError("Required disk parameter '%s' missing" %
2291                                    constants.IDISK_SIZE, errors.ECODE_INVAL)
2292
2293       try:
2294         size = int(size)
2295       except (TypeError, ValueError), err:
2296         raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
2297                                    errors.ECODE_INVAL)
2298
2299       params[constants.IDISK_SIZE] = size
2300       name = params.get(constants.IDISK_NAME, None)
2301       if name is not None and name.lower() == constants.VALUE_NONE:
2302         params[constants.IDISK_NAME] = None
2303
2304     elif op == constants.DDM_MODIFY:
2305       if constants.IDISK_SIZE in params:
2306         raise errors.OpPrereqError("Disk size change not possible, use"
2307                                    " grow-disk", errors.ECODE_INVAL)
2308
2309       # Disk modification supports changing only the disk name and mode.
2310       # Changing arbitrary parameters is allowed only for ext disk template",
2311       if self.instance.disk_template != constants.DT_EXT:
2312         utils.ForceDictType(params, constants.MODIFIABLE_IDISK_PARAMS_TYPES)
2313
2314       name = params.get(constants.IDISK_NAME, None)
2315       if name is not None and name.lower() == constants.VALUE_NONE:
2316         params[constants.IDISK_NAME] = None
2317
2318   @staticmethod
2319   def _VerifyNicModification(op, params):
2320     """Verifies a network interface modification.
2321
2322     """
2323     if op in (constants.DDM_ADD, constants.DDM_MODIFY):
2324       ip = params.get(constants.INIC_IP, None)
2325       name = params.get(constants.INIC_NAME, None)
2326       req_net = params.get(constants.INIC_NETWORK, None)
2327       link = params.get(constants.NIC_LINK, None)
2328       mode = params.get(constants.NIC_MODE, None)
2329       if name is not None and name.lower() == constants.VALUE_NONE:
2330         params[constants.INIC_NAME] = None
2331       if req_net is not None:
2332         if req_net.lower() == constants.VALUE_NONE:
2333           params[constants.INIC_NETWORK] = None
2334           req_net = None
2335         elif link is not None or mode is not None:
2336           raise errors.OpPrereqError("If network is given"
2337                                      " mode or link should not",
2338                                      errors.ECODE_INVAL)
2339
2340       if op == constants.DDM_ADD:
2341         macaddr = params.get(constants.INIC_MAC, None)
2342         if macaddr is None:
2343           params[constants.INIC_MAC] = constants.VALUE_AUTO
2344
2345       if ip is not None:
2346         if ip.lower() == constants.VALUE_NONE:
2347           params[constants.INIC_IP] = None
2348         else:
2349           if ip.lower() == constants.NIC_IP_POOL:
2350             if op == constants.DDM_ADD and req_net is None:
2351               raise errors.OpPrereqError("If ip=pool, parameter network"
2352                                          " cannot be none",
2353                                          errors.ECODE_INVAL)
2354           else:
2355             if not netutils.IPAddress.IsValid(ip):
2356               raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
2357                                          errors.ECODE_INVAL)
2358
2359       if constants.INIC_MAC in params:
2360         macaddr = params[constants.INIC_MAC]
2361         if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2362           macaddr = utils.NormalizeAndValidateMac(macaddr)
2363
2364         if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
2365           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
2366                                      " modifying an existing NIC",
2367                                      errors.ECODE_INVAL)
2368
2369   def CheckArguments(self):
2370     if not (self.op.nics or self.op.disks or self.op.disk_template or
2371             self.op.hvparams or self.op.beparams or self.op.os_name or
2372             self.op.osparams or self.op.offline is not None or
2373             self.op.runtime_mem or self.op.pnode):
2374       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
2375
2376     if self.op.hvparams:
2377       CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
2378                            "hypervisor", "instance", "cluster")
2379
2380     self.op.disks = self._UpgradeDiskNicMods(
2381       "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
2382     self.op.nics = self._UpgradeDiskNicMods(
2383       "NIC", self.op.nics, opcodes.OpInstanceSetParams.TestNicModifications)
2384
2385     if self.op.disks and self.op.disk_template is not None:
2386       raise errors.OpPrereqError("Disk template conversion and other disk"
2387                                  " changes not supported at the same time",
2388                                  errors.ECODE_INVAL)
2389
2390     if (self.op.disk_template and
2391         self.op.disk_template in constants.DTS_INT_MIRROR and
2392         self.op.remote_node is None):
2393       raise errors.OpPrereqError("Changing the disk template to a mirrored"
2394                                  " one requires specifying a secondary node",
2395                                  errors.ECODE_INVAL)
2396
2397     # Check NIC modifications
2398     self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
2399                     self._VerifyNicModification)
2400
2401     if self.op.pnode:
2402       self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
2403
2404   def ExpandNames(self):
2405     self._ExpandAndLockInstance()
2406     self.needed_locks[locking.LEVEL_NODEGROUP] = []
2407     # Can't even acquire node locks in shared mode as upcoming changes in
2408     # Ganeti 2.6 will start to modify the node object on disk conversion
2409     self.needed_locks[locking.LEVEL_NODE] = []
2410     self.needed_locks[locking.LEVEL_NODE_RES] = []
2411     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2412     # Look node group to look up the ipolicy
2413     self.share_locks[locking.LEVEL_NODEGROUP] = 1
2414
2415   def DeclareLocks(self, level):
2416     if level == locking.LEVEL_NODEGROUP:
2417       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
2418       # Acquire locks for the instance's nodegroups optimistically. Needs
2419       # to be verified in CheckPrereq
2420       self.needed_locks[locking.LEVEL_NODEGROUP] = \
2421         self.cfg.GetInstanceNodeGroups(self.op.instance_name)
2422     elif level == locking.LEVEL_NODE:
2423       self._LockInstancesNodes()
2424       if self.op.disk_template and self.op.remote_node:
2425         self.op.remote_node = ExpandNodeName(self.cfg, self.op.remote_node)
2426         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
2427     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
2428       # Copy node locks
2429       self.needed_locks[locking.LEVEL_NODE_RES] = \
2430         CopyLockList(self.needed_locks[locking.LEVEL_NODE])
2431
2432   def BuildHooksEnv(self):
2433     """Build hooks env.
2434
2435     This runs on the master, primary and secondaries.
2436
2437     """
2438     args = {}
2439     if constants.BE_MINMEM in self.be_new:
2440       args["minmem"] = self.be_new[constants.BE_MINMEM]
2441     if constants.BE_MAXMEM in self.be_new:
2442       args["maxmem"] = self.be_new[constants.BE_MAXMEM]
2443     if constants.BE_VCPUS in self.be_new:
2444       args["vcpus"] = self.be_new[constants.BE_VCPUS]
2445     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
2446     # information at all.
2447
2448     if self._new_nics is not None:
2449       nics = []
2450
2451       for nic in self._new_nics:
2452         n = copy.deepcopy(nic)
2453         nicparams = self.cluster.SimpleFillNIC(n.nicparams)
2454         n.nicparams = nicparams
2455         nics.append(NICToTuple(self, n))
2456
2457       args["nics"] = nics
2458
2459     env = BuildInstanceHookEnvByObject(self, self.instance, override=args)
2460     if self.op.disk_template:
2461       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
2462     if self.op.runtime_mem:
2463       env["RUNTIME_MEMORY"] = self.op.runtime_mem
2464
2465     return env
2466
2467   def BuildHooksNodes(self):
2468     """Build hooks nodes.
2469
2470     """
2471     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
2472     return (nl, nl)
2473
2474   def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
2475                               old_params, cluster, pnode):
2476
2477     update_params_dict = dict([(key, params[key])
2478                                for key in constants.NICS_PARAMETERS
2479                                if key in params])
2480
2481     req_link = update_params_dict.get(constants.NIC_LINK, None)
2482     req_mode = update_params_dict.get(constants.NIC_MODE, None)
2483
2484     new_net_uuid = None
2485     new_net_uuid_or_name = params.get(constants.INIC_NETWORK, old_net_uuid)
2486     if new_net_uuid_or_name:
2487       new_net_uuid = self.cfg.LookupNetwork(new_net_uuid_or_name)
2488       new_net_obj = self.cfg.GetNetwork(new_net_uuid)
2489
2490     if old_net_uuid:
2491       old_net_obj = self.cfg.GetNetwork(old_net_uuid)
2492
2493     if new_net_uuid:
2494       netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode)
2495       if not netparams:
2496         raise errors.OpPrereqError("No netparams found for the network"
2497                                    " %s, probably not connected" %
2498                                    new_net_obj.name, errors.ECODE_INVAL)
2499       new_params = dict(netparams)
2500     else:
2501       new_params = GetUpdatedParams(old_params, update_params_dict)
2502
2503     utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
2504
2505     new_filled_params = cluster.SimpleFillNIC(new_params)
2506     objects.NIC.CheckParameterSyntax(new_filled_params)
2507
2508     new_mode = new_filled_params[constants.NIC_MODE]
2509     if new_mode == constants.NIC_MODE_BRIDGED:
2510       bridge = new_filled_params[constants.NIC_LINK]
2511       msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
2512       if msg:
2513         msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
2514         if self.op.force:
2515           self.warn.append(msg)
2516         else:
2517           raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
2518
2519     elif new_mode == constants.NIC_MODE_ROUTED:
2520       ip = params.get(constants.INIC_IP, old_ip)
2521
2522     elif new_mode == constants.NIC_MODE_OVS:
2523       # TODO: check OVS link
2524       self.LogInfo("OVS links are currently not checked for correctness")
2525
2526     if constants.INIC_MAC in params:
2527       mac = params[constants.INIC_MAC]
2528       if mac is None:
2529         raise errors.OpPrereqError("Cannot unset the NIC MAC address",
2530                                    errors.ECODE_INVAL)
2531       elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2532         # otherwise generate the MAC address
2533         params[constants.INIC_MAC] = \
2534           self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2535       else:
2536         # or validate/reserve the current one
2537         try:
2538           self.cfg.ReserveMAC(mac, self.proc.GetECId())
2539         except errors.ReservationError:
2540           raise errors.OpPrereqError("MAC address '%s' already in use"
2541                                      " in cluster" % mac,
2542                                      errors.ECODE_NOTUNIQUE)
2543     elif new_net_uuid != old_net_uuid:
2544
2545       def get_net_prefix(net_uuid):
2546         mac_prefix = None
2547         if net_uuid:
2548           nobj = self.cfg.GetNetwork(net_uuid)
2549           mac_prefix = nobj.mac_prefix
2550
2551         return mac_prefix
2552
2553       new_prefix = get_net_prefix(new_net_uuid)
2554       old_prefix = get_net_prefix(old_net_uuid)
2555       if old_prefix != new_prefix:
2556         params[constants.INIC_MAC] = \
2557           self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2558
2559     # if there is a change in (ip, network) tuple
2560     new_ip = params.get(constants.INIC_IP, old_ip)
2561     if (new_ip, new_net_uuid) != (old_ip, old_net_uuid):
2562       if new_ip:
2563         # if IP is pool then require a network and generate one IP
2564         if new_ip.lower() == constants.NIC_IP_POOL:
2565           if new_net_uuid:
2566             try:
2567               new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId())
2568             except errors.ReservationError:
2569               raise errors.OpPrereqError("Unable to get a free IP"
2570                                          " from the address pool",
2571                                          errors.ECODE_STATE)
2572             self.LogInfo("Chose IP %s from network %s",
2573                          new_ip,
2574                          new_net_obj.name)
2575             params[constants.INIC_IP] = new_ip
2576           else:
2577             raise errors.OpPrereqError("ip=pool, but no network found",
2578                                        errors.ECODE_INVAL)
2579         # Reserve new IP if in the new network if any
2580         elif new_net_uuid:
2581           try:
2582             self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId(),
2583                                check=self.op.conflicts_check)
2584             self.LogInfo("Reserving IP %s in network %s",
2585                          new_ip, new_net_obj.name)
2586           except errors.ReservationError:
2587             raise errors.OpPrereqError("IP %s not available in network %s" %
2588                                        (new_ip, new_net_obj.name),
2589                                        errors.ECODE_NOTUNIQUE)
2590         # new network is None so check if new IP is a conflicting IP
2591         elif self.op.conflicts_check:
2592           _CheckForConflictingIp(self, new_ip, pnode)
2593
2594       # release old IP if old network is not None
2595       if old_ip and old_net_uuid:
2596         try:
2597           self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId())
2598         except errors.AddressPoolError:
2599           logging.warning("Release IP %s not contained in network %s",
2600                           old_ip, old_net_obj.name)
2601
2602     # there are no changes in (ip, network) tuple and old network is not None
2603     elif (old_net_uuid is not None and
2604           (req_link is not None or req_mode is not None)):
2605       raise errors.OpPrereqError("Not allowed to change link or mode of"
2606                                  " a NIC that is connected to a network",
2607                                  errors.ECODE_INVAL)
2608
2609     private.params = new_params
2610     private.filled = new_filled_params
2611
2612   def _PreCheckDiskTemplate(self, pnode_info):
2613     """CheckPrereq checks related to a new disk template."""
2614     # Arguments are passed to avoid configuration lookups
2615     instance = self.instance
2616     pnode = instance.primary_node
2617     cluster = self.cluster
2618     if instance.disk_template == self.op.disk_template:
2619       raise errors.OpPrereqError("Instance already has disk template %s" %
2620                                  instance.disk_template, errors.ECODE_INVAL)
2621
2622     if (instance.disk_template,
2623         self.op.disk_template) not in self._DISK_CONVERSIONS:
2624       raise errors.OpPrereqError("Unsupported disk template conversion from"
2625                                  " %s to %s" % (instance.disk_template,
2626                                                 self.op.disk_template),
2627                                  errors.ECODE_INVAL)
2628     CheckInstanceState(self, instance, INSTANCE_DOWN,
2629                        msg="cannot change disk template")
2630     if self.op.disk_template in constants.DTS_INT_MIRROR:
2631       if self.op.remote_node == pnode:
2632         raise errors.OpPrereqError("Given new secondary node %s is the same"
2633                                    " as the primary node of the instance" %
2634                                    self.op.remote_node, errors.ECODE_STATE)
2635       CheckNodeOnline(self, self.op.remote_node)
2636       CheckNodeNotDrained(self, self.op.remote_node)
2637       # FIXME: here we assume that the old instance type is DT_PLAIN
2638       assert instance.disk_template == constants.DT_PLAIN
2639       disks = [{constants.IDISK_SIZE: d.size,
2640                 constants.IDISK_VG: d.logical_id[0]}
2641                for d in instance.disks]
2642       required = ComputeDiskSizePerVG(self.op.disk_template, disks)
2643       CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
2644
2645       snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
2646       snode_group = self.cfg.GetNodeGroup(snode_info.group)
2647       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2648                                                               snode_group)
2649       CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info, self.cfg,
2650                              ignore=self.op.ignore_ipolicy)
2651       if pnode_info.group != snode_info.group:
2652         self.LogWarning("The primary and secondary nodes are in two"
2653                         " different node groups; the disk parameters"
2654                         " from the first disk's node group will be"
2655                         " used")
2656
2657     if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
2658       # Make sure none of the nodes require exclusive storage
2659       nodes = [pnode_info]
2660       if self.op.disk_template in constants.DTS_INT_MIRROR:
2661         assert snode_info
2662         nodes.append(snode_info)
2663       has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
2664       if compat.any(map(has_es, nodes)):
2665         errmsg = ("Cannot convert disk template from %s to %s when exclusive"
2666                   " storage is enabled" % (instance.disk_template,
2667                                            self.op.disk_template))
2668         raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
2669
2670   # too many local variables
2671   # pylint: disable=R0914
2672   def CheckPrereq(self):
2673     """Check prerequisites.
2674
2675     This only checks the instance list against the existing names.
2676
2677     """
2678     assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
2679     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
2680
2681     cluster = self.cluster = self.cfg.GetClusterInfo()
2682     assert self.instance is not None, \
2683       "Cannot retrieve locked instance %s" % self.op.instance_name
2684
2685     pnode = instance.primary_node
2686
2687     self.warn = []
2688
2689     if (self.op.pnode is not None and self.op.pnode != pnode and
2690         not self.op.force):
2691       # verify that the instance is not up
2692       instance_info = self.rpc.call_instance_info(pnode, instance.name,
2693                                                   instance.hypervisor)
2694       if instance_info.fail_msg:
2695         self.warn.append("Can't get instance runtime information: %s" %
2696                          instance_info.fail_msg)
2697       elif instance_info.payload:
2698         raise errors.OpPrereqError("Instance is still running on %s" % pnode,
2699                                    errors.ECODE_STATE)
2700
2701     assert pnode in self.owned_locks(locking.LEVEL_NODE)
2702     nodelist = list(instance.all_nodes)
2703     pnode_info = self.cfg.GetNodeInfo(pnode)
2704     self.diskparams = self.cfg.GetInstanceDiskParams(instance)
2705
2706     #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
2707     assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
2708     group_info = self.cfg.GetNodeGroup(pnode_info.group)
2709
2710     # dictionary with instance information after the modification
2711     ispec = {}
2712
2713     # Check disk modifications. This is done here and not in CheckArguments
2714     # (as with NICs), because we need to know the instance's disk template
2715     if instance.disk_template == constants.DT_EXT:
2716       self._CheckMods("disk", self.op.disks, {},
2717                       self._VerifyDiskModification)
2718     else:
2719       self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
2720                       self._VerifyDiskModification)
2721
2722     # Prepare disk/NIC modifications
2723     self.diskmod = _PrepareContainerMods(self.op.disks, None)
2724     self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
2725
2726     # Check the validity of the `provider' parameter
2727     if instance.disk_template in constants.DT_EXT:
2728       for mod in self.diskmod:
2729         ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2730         if mod[0] == constants.DDM_ADD:
2731           if ext_provider is None:
2732             raise errors.OpPrereqError("Instance template is '%s' and parameter"
2733                                        " '%s' missing, during disk add" %
2734                                        (constants.DT_EXT,
2735                                         constants.IDISK_PROVIDER),
2736                                        errors.ECODE_NOENT)
2737         elif mod[0] == constants.DDM_MODIFY:
2738           if ext_provider:
2739             raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
2740                                        " modification" %
2741                                        constants.IDISK_PROVIDER,
2742                                        errors.ECODE_INVAL)
2743     else:
2744       for mod in self.diskmod:
2745         ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2746         if ext_provider is not None:
2747           raise errors.OpPrereqError("Parameter '%s' is only valid for"
2748                                      " instances of type '%s'" %
2749                                      (constants.IDISK_PROVIDER,
2750                                       constants.DT_EXT),
2751                                      errors.ECODE_INVAL)
2752
2753     if self.op.hotplug or self.op.hotplug_if_possible:
2754       result = self.rpc.call_hotplug_supported(self.instance.primary_node,
2755                                                self.instance)
2756       if result.fail_msg:
2757         if self.op.hotplug:
2758           result.Raise("Hotplug is not possible: %s" % result.fail_msg,
2759                        prereq=True)
2760         else:
2761           self.LogWarning(result.fail_msg)
2762           self.op.hotplug = False
2763           self.LogInfo("Modification will take place without hotplugging.")
2764       else:
2765         self.op.hotplug = True
2766
2767     # OS change
2768     if self.op.os_name and not self.op.force:
2769       CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
2770                      self.op.force_variant)
2771       instance_os = self.op.os_name
2772     else:
2773       instance_os = instance.os
2774
2775     assert not (self.op.disk_template and self.op.disks), \
2776       "Can't modify disk template and apply disk changes at the same time"
2777
2778     if self.op.disk_template:
2779       self._PreCheckDiskTemplate(pnode_info)
2780
2781     # hvparams processing
2782     if self.op.hvparams:
2783       hv_type = instance.hypervisor
2784       i_hvdict = GetUpdatedParams(instance.hvparams, self.op.hvparams)
2785       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
2786       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
2787
2788       # local check
2789       hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
2790       CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
2791       self.hv_proposed = self.hv_new = hv_new # the new actual values
2792       self.hv_inst = i_hvdict # the new dict (without defaults)
2793     else:
2794       self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
2795                                               instance.hvparams)
2796       self.hv_new = self.hv_inst = {}
2797
2798     # beparams processing
2799     if self.op.beparams:
2800       i_bedict = GetUpdatedParams(instance.beparams, self.op.beparams,
2801                                   use_none=True)
2802       objects.UpgradeBeParams(i_bedict)
2803       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
2804       be_new = cluster.SimpleFillBE(i_bedict)
2805       self.be_proposed = self.be_new = be_new # the new actual values
2806       self.be_inst = i_bedict # the new dict (without defaults)
2807     else:
2808       self.be_new = self.be_inst = {}
2809       self.be_proposed = cluster.SimpleFillBE(instance.beparams)
2810     be_old = cluster.FillBE(instance)
2811
2812     # CPU param validation -- checking every time a parameter is
2813     # changed to cover all cases where either CPU mask or vcpus have
2814     # changed
2815     if (constants.BE_VCPUS in self.be_proposed and
2816         constants.HV_CPU_MASK in self.hv_proposed):
2817       cpu_list = \
2818         utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
2819       # Verify mask is consistent with number of vCPUs. Can skip this
2820       # test if only 1 entry in the CPU mask, which means same mask
2821       # is applied to all vCPUs.
2822       if (len(cpu_list) > 1 and
2823           len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
2824         raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
2825                                    " CPU mask [%s]" %
2826                                    (self.be_proposed[constants.BE_VCPUS],
2827                                     self.hv_proposed[constants.HV_CPU_MASK]),
2828                                    errors.ECODE_INVAL)
2829
2830       # Only perform this test if a new CPU mask is given
2831       if constants.HV_CPU_MASK in self.hv_new:
2832         # Calculate the largest CPU number requested
2833         max_requested_cpu = max(map(max, cpu_list))
2834         # Check that all of the instance's nodes have enough physical CPUs to
2835         # satisfy the requested CPU mask
2836         _CheckNodesPhysicalCPUs(self, instance.all_nodes,
2837                                 max_requested_cpu + 1, instance.hypervisor)
2838
2839     # osparams processing
2840     if self.op.osparams:
2841       i_osdict = GetUpdatedParams(instance.osparams, self.op.osparams)
2842       CheckOSParams(self, True, nodelist, instance_os, i_osdict)
2843       self.os_inst = i_osdict # the new dict (without defaults)
2844     else:
2845       self.os_inst = {}
2846
2847     #TODO(dynmem): do the appropriate check involving MINMEM
2848     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
2849         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
2850       mem_check_list = [pnode]
2851       if be_new[constants.BE_AUTO_BALANCE]:
2852         # either we changed auto_balance to yes or it was from before
2853         mem_check_list.extend(instance.secondary_nodes)
2854       instance_info = self.rpc.call_instance_info(pnode, instance.name,
2855                                                   instance.hypervisor)
2856       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
2857                                          [instance.hypervisor], False)
2858       pninfo = nodeinfo[pnode]
2859       msg = pninfo.fail_msg
2860       if msg:
2861         # Assume the primary node is unreachable and go ahead
2862         self.warn.append("Can't get info from primary node %s: %s" %
2863                          (pnode, msg))
2864       else:
2865         (_, _, (pnhvinfo, )) = pninfo.payload
2866         if not isinstance(pnhvinfo.get("memory_free", None), int):
2867           self.warn.append("Node data from primary node %s doesn't contain"
2868                            " free memory information" % pnode)
2869         elif instance_info.fail_msg:
2870           self.warn.append("Can't get instance runtime information: %s" %
2871                            instance_info.fail_msg)
2872         else:
2873           if instance_info.payload:
2874             current_mem = int(instance_info.payload["memory"])
2875           else:
2876             # Assume instance not running
2877             # (there is a slight race condition here, but it's not very
2878             # probable, and we have no other way to check)
2879             # TODO: Describe race condition
2880             current_mem = 0
2881           #TODO(dynmem): do the appropriate check involving MINMEM
2882           miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
2883                       pnhvinfo["memory_free"])
2884           if miss_mem > 0:
2885             raise errors.OpPrereqError("This change will prevent the instance"
2886                                        " from starting, due to %d MB of memory"
2887                                        " missing on its primary node" %
2888                                        miss_mem, errors.ECODE_NORES)
2889
2890       if be_new[constants.BE_AUTO_BALANCE]:
2891         for node, nres in nodeinfo.items():
2892           if node not in instance.secondary_nodes:
2893             continue
2894           nres.Raise("Can't get info from secondary node %s" % node,
2895                      prereq=True, ecode=errors.ECODE_STATE)
2896           (_, _, (nhvinfo, )) = nres.payload
2897           if not isinstance(nhvinfo.get("memory_free", None), int):
2898             raise errors.OpPrereqError("Secondary node %s didn't return free"
2899                                        " memory information" % node,
2900                                        errors.ECODE_STATE)
2901           #TODO(dynmem): do the appropriate check involving MINMEM
2902           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
2903             raise errors.OpPrereqError("This change will prevent the instance"
2904                                        " from failover to its secondary node"
2905                                        " %s, due to not enough memory" % node,
2906                                        errors.ECODE_STATE)
2907
2908     if self.op.runtime_mem:
2909       remote_info = self.rpc.call_instance_info(instance.primary_node,
2910                                                 instance.name,
2911                                                 instance.hypervisor)
2912       remote_info.Raise("Error checking node %s" % instance.primary_node)
2913       if not remote_info.payload: # not running already
2914         raise errors.OpPrereqError("Instance %s is not running" %
2915                                    instance.name, errors.ECODE_STATE)
2916
2917       current_memory = remote_info.payload["memory"]
2918       if (not self.op.force and
2919            (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
2920             self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
2921         raise errors.OpPrereqError("Instance %s must have memory between %d"
2922                                    " and %d MB of memory unless --force is"
2923                                    " given" %
2924                                    (instance.name,
2925                                     self.be_proposed[constants.BE_MINMEM],
2926                                     self.be_proposed[constants.BE_MAXMEM]),
2927                                    errors.ECODE_INVAL)
2928
2929       delta = self.op.runtime_mem - current_memory
2930       if delta > 0:
2931         CheckNodeFreeMemory(self, instance.primary_node,
2932                             "ballooning memory for instance %s" %
2933                             instance.name, delta, instance.hypervisor)
2934
2935     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
2936       raise errors.OpPrereqError("Disk operations not supported for"
2937                                  " diskless instances", errors.ECODE_INVAL)
2938
2939     def _PrepareNicCreate(_, params, private):
2940       self._PrepareNicModification(params, private, None, None,
2941                                    {}, cluster, pnode)
2942       return (None, None)
2943
2944     def _PrepareNicMod(_, nic, params, private):
2945       self._PrepareNicModification(params, private, nic.ip, nic.network,
2946                                    nic.nicparams, cluster, pnode)
2947       return None
2948
2949     def _PrepareNicRemove(_, params, __):
2950       ip = params.ip
2951       net = params.network
2952       if net is not None and ip is not None:
2953         self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
2954
2955     # Verify NIC changes (operating on copy)
2956     nics = instance.nics[:]
2957     _ApplyContainerMods("NIC", nics, None, self.nicmod,
2958                         _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
2959     if len(nics) > constants.MAX_NICS:
2960       raise errors.OpPrereqError("Instance has too many network interfaces"
2961                                  " (%d), cannot add more" % constants.MAX_NICS,
2962                                  errors.ECODE_STATE)
2963
2964     def _PrepareDiskMod(_, disk, params, __):
2965       disk.name = params.get(constants.IDISK_NAME, None)
2966
2967     # Verify disk changes (operating on a copy)
2968     disks = copy.deepcopy(instance.disks)
2969     _ApplyContainerMods("disk", disks, None, self.diskmod, None,
2970                         _PrepareDiskMod, None)
2971     utils.ValidateDeviceNames("disk", disks)
2972     if len(disks) > constants.MAX_DISKS:
2973       raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
2974                                  " more" % constants.MAX_DISKS,
2975                                  errors.ECODE_STATE)
2976     disk_sizes = [disk.size for disk in instance.disks]
2977     disk_sizes.extend(params["size"] for (op, idx, params, private) in
2978                       self.diskmod if op == constants.DDM_ADD)
2979     ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
2980     ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
2981
2982     if self.op.offline is not None and self.op.offline:
2983       CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE,
2984                          msg="can't change to offline")
2985
2986     # Pre-compute NIC changes (necessary to use result in hooks)
2987     self._nic_chgdesc = []
2988     if self.nicmod:
2989       # Operate on copies as this is still in prereq
2990       nics = [nic.Copy() for nic in instance.nics]
2991       _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
2992                           self._CreateNewNic, self._ApplyNicMods,
2993                           self._RemoveNic)
2994       # Verify that NIC names are unique and valid
2995       utils.ValidateDeviceNames("NIC", nics)
2996       self._new_nics = nics
2997       ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
2998     else:
2999       self._new_nics = None
3000       ispec[constants.ISPEC_NIC_COUNT] = len(instance.nics)
3001
3002     if not self.op.ignore_ipolicy:
3003       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
3004                                                               group_info)
3005
3006       # Fill ispec with backend parameters
3007       ispec[constants.ISPEC_SPINDLE_USE] = \
3008         self.be_new.get(constants.BE_SPINDLE_USE, None)
3009       ispec[constants.ISPEC_CPU_COUNT] = self.be_new.get(constants.BE_VCPUS,
3010                                                          None)
3011
3012       # Copy ispec to verify parameters with min/max values separately
3013       if self.op.disk_template:
3014         new_disk_template = self.op.disk_template
3015       else:
3016         new_disk_template = instance.disk_template
3017       ispec_max = ispec.copy()
3018       ispec_max[constants.ISPEC_MEM_SIZE] = \
3019         self.be_new.get(constants.BE_MAXMEM, None)
3020       res_max = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_max,
3021                                                      new_disk_template)
3022       ispec_min = ispec.copy()
3023       ispec_min[constants.ISPEC_MEM_SIZE] = \
3024         self.be_new.get(constants.BE_MINMEM, None)
3025       res_min = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
3026                                                      new_disk_template)
3027
3028       if (res_max or res_min):
3029         # FIXME: Improve error message by including information about whether
3030         # the upper or lower limit of the parameter fails the ipolicy.
3031         msg = ("Instance allocation to group %s (%s) violates policy: %s" %
3032                (group_info, group_info.name,
3033                 utils.CommaJoin(set(res_max + res_min))))
3034         raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
3035
3036   def _ConvertPlainToDrbd(self, feedback_fn):
3037     """Converts an instance from plain to drbd.
3038
3039     """
3040     feedback_fn("Converting template to drbd")
3041     instance = self.instance
3042     pnode = instance.primary_node
3043     snode = self.op.remote_node
3044
3045     assert instance.disk_template == constants.DT_PLAIN
3046
3047     # create a fake disk info for _GenerateDiskTemplate
3048     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
3049                   constants.IDISK_VG: d.logical_id[0],
3050                   constants.IDISK_NAME: d.name}
3051                  for d in instance.disks]
3052     new_disks = GenerateDiskTemplate(self, self.op.disk_template,
3053                                      instance.name, pnode, [snode],
3054                                      disk_info, None, None, 0, feedback_fn,
3055                                      self.diskparams)
3056     anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
3057                                         self.diskparams)
3058     p_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, pnode)
3059     s_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, snode)
3060     info = GetInstanceInfoText(instance)
3061     feedback_fn("Creating additional volumes...")
3062     # first, create the missing data and meta devices
3063     for disk in anno_disks:
3064       # unfortunately this is... not too nice
3065       CreateSingleBlockDev(self, pnode, instance, disk.children[1],
3066                            info, True, p_excl_stor)
3067       for child in disk.children:
3068         CreateSingleBlockDev(self, snode, instance, child, info, True,
3069                              s_excl_stor)
3070     # at this stage, all new LVs have been created, we can rename the
3071     # old ones
3072     feedback_fn("Renaming original volumes...")
3073     rename_list = [(o, n.children[0].logical_id)
3074                    for (o, n) in zip(instance.disks, new_disks)]
3075     result = self.rpc.call_blockdev_rename(pnode, rename_list)
3076     result.Raise("Failed to rename original LVs")
3077
3078     feedback_fn("Initializing DRBD devices...")
3079     # all child devices are in place, we can now create the DRBD devices
3080     try:
3081       for disk in anno_disks:
3082         for (node, excl_stor) in [(pnode, p_excl_stor), (snode, s_excl_stor)]:
3083           f_create = node == pnode
3084           CreateSingleBlockDev(self, node, instance, disk, info, f_create,
3085                                excl_stor)
3086     except errors.GenericError, e:
3087       feedback_fn("Initializing of DRBD devices failed;"
3088                   " renaming back original volumes...")
3089       for disk in new_disks:
3090         self.cfg.SetDiskID(disk, pnode)
3091       rename_back_list = [(n.children[0], o.logical_id)
3092                           for (n, o) in zip(new_disks, instance.disks)]
3093       result = self.rpc.call_blockdev_rename(pnode, rename_back_list)
3094       result.Raise("Failed to rename LVs back after error %s" % str(e))
3095       raise
3096
3097     # at this point, the instance has been modified
3098     instance.disk_template = constants.DT_DRBD8
3099     instance.disks = new_disks
3100     self.cfg.Update(instance, feedback_fn)
3101
3102     # Release node locks while waiting for sync
3103     ReleaseLocks(self, locking.LEVEL_NODE)
3104
3105     # disks are created, waiting for sync
3106     disk_abort = not WaitForSync(self, instance,
3107                                  oneshot=not self.op.wait_for_sync)
3108     if disk_abort:
3109       raise errors.OpExecError("There are some degraded disks for"
3110                                " this instance, please cleanup manually")
3111
3112     # Node resource locks will be released by caller
3113
3114   def _ConvertDrbdToPlain(self, feedback_fn):
3115     """Converts an instance from drbd to plain.
3116
3117     """
3118     instance = self.instance
3119
3120     assert len(instance.secondary_nodes) == 1
3121     assert instance.disk_template == constants.DT_DRBD8
3122
3123     pnode = instance.primary_node
3124     snode = instance.secondary_nodes[0]
3125     feedback_fn("Converting template to plain")
3126
3127     old_disks = AnnotateDiskParams(instance, instance.disks, self.cfg)
3128     new_disks = [d.children[0] for d in instance.disks]
3129
3130     # copy over size, mode and name
3131     for parent, child in zip(old_disks, new_disks):
3132       child.size = parent.size
3133       child.mode = parent.mode
3134       child.name = parent.name
3135
3136     # this is a DRBD disk, return its port to the pool
3137     # NOTE: this must be done right before the call to cfg.Update!
3138     for disk in old_disks:
3139       tcp_port = disk.logical_id[2]
3140       self.cfg.AddTcpUdpPort(tcp_port)
3141
3142     # update instance structure
3143     instance.disks = new_disks
3144     instance.disk_template = constants.DT_PLAIN
3145     _UpdateIvNames(0, instance.disks)
3146     self.cfg.Update(instance, feedback_fn)
3147
3148     # Release locks in case removing disks takes a while
3149     ReleaseLocks(self, locking.LEVEL_NODE)
3150
3151     feedback_fn("Removing volumes on the secondary node...")
3152     for disk in old_disks:
3153       self.cfg.SetDiskID(disk, snode)
3154       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
3155       if msg:
3156         self.LogWarning("Could not remove block device %s on node %s,"
3157                         " continuing anyway: %s", disk.iv_name, snode, msg)
3158
3159     feedback_fn("Removing unneeded volumes on the primary node...")
3160     for idx, disk in enumerate(old_disks):
3161       meta = disk.children[1]
3162       self.cfg.SetDiskID(meta, pnode)
3163       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
3164       if msg:
3165         self.LogWarning("Could not remove metadata for disk %d on node %s,"
3166                         " continuing anyway: %s", idx, pnode, msg)
3167
3168   def _HotplugDevice(self, action, dev_type, device, extra, seq):
3169     self.LogInfo("Trying to hotplug device...")
3170     msg = "hotplug:"
3171     result = self.rpc.call_hotplug_device(self.instance.primary_node,
3172                                           self.instance, action, dev_type,
3173                                           (device, self.instance),
3174                                           extra, seq)
3175     if result.fail_msg:
3176       self.LogWarning("Could not hotplug device: %s" % result.fail_msg)
3177       self.LogInfo("Continuing execution..")
3178       msg += "failed"
3179     else:
3180       self.LogInfo("Hotplug done.")
3181       msg += "done"
3182     return msg
3183
3184   def _CreateNewDisk(self, idx, params, _):
3185     """Creates a new disk.
3186
3187     """
3188     instance = self.instance
3189
3190     # add a new disk
3191     if instance.disk_template in constants.DTS_FILEBASED:
3192       (file_driver, file_path) = instance.disks[0].logical_id
3193       file_path = os.path.dirname(file_path)
3194     else:
3195       file_driver = file_path = None
3196
3197     disk = \
3198       GenerateDiskTemplate(self, instance.disk_template, instance.name,
3199                            instance.primary_node, instance.secondary_nodes,
3200                            [params], file_path, file_driver, idx,
3201                            self.Log, self.diskparams)[0]
3202
3203     new_disks = CreateDisks(self, instance, disks=[disk])
3204
3205     if self.cluster.prealloc_wipe_disks:
3206       # Wipe new disk
3207       WipeOrCleanupDisks(self, instance,
3208                          disks=[(idx, disk, 0)],
3209                          cleanup=new_disks)
3210
3211     changes = [
3212       ("disk/%d" % idx,
3213       "add:size=%s,mode=%s" % (disk.size, disk.mode)),
3214       ]
3215     if self.op.hotplug:
3216       self.cfg.SetDiskID(disk, self.instance.primary_node)
3217       result = self.rpc.call_blockdev_assemble(self.instance.primary_node,
3218                                                (disk, self.instance),
3219                                                self.instance.name, True, idx)
3220       if result.fail_msg:
3221         changes.append(("disk/%d" % idx, "assemble:failed"))
3222         self.LogWarning("Can't assemble newly created disk %d: %s",
3223                         idx, result.fail_msg)
3224       else:
3225         _, link_name = result.payload
3226         msg = self._HotplugDevice(constants.HOTPLUG_ACTION_ADD,
3227                                   constants.HOTPLUG_TARGET_DISK,
3228                                   disk, link_name, idx)
3229         changes.append(("disk/%d" % idx, msg))
3230
3231     return (disk, changes)
3232
3233   def _ModifyDisk(self, idx, disk, params, _):
3234     """Modifies a disk.
3235
3236     """
3237     changes = []
3238     if constants.IDISK_MODE in params:
3239       disk.mode = params.get(constants.IDISK_MODE)
3240       changes.append(("disk.mode/%d" % idx, disk.mode))
3241
3242     if constants.IDISK_NAME in params:
3243       disk.name = params.get(constants.IDISK_NAME)
3244       changes.append(("disk.name/%d" % idx, disk.name))
3245
3246     # Modify arbitrary params in case instance template is ext
3247     for key, value in params.iteritems():
3248       if (key not in constants.MODIFIABLE_IDISK_PARAMS and
3249           self.instance.disk_template == constants.DT_EXT):
3250         # stolen from GetUpdatedParams: default means reset/delete
3251         if value.lower() == constants.VALUE_DEFAULT:
3252           try:
3253             del disk.params[key]
3254           except KeyError:
3255             pass
3256         else:
3257           disk.params[key] = value
3258         changes.append(("disk.params:%s/%d" % (key, idx), value))
3259
3260     return changes
3261
3262   def _RemoveDisk(self, idx, root, _):
3263     """Removes a disk.
3264
3265     """
3266     hotmsg = ""
3267     if self.op.hotplug:
3268       hotmsg = self._HotplugDevice(constants.HOTPLUG_ACTION_REMOVE,
3269                                    constants.HOTPLUG_TARGET_DISK,
3270                                    root, None, idx)
3271       ShutdownInstanceDisks(self, self.instance, [root])
3272
3273     (anno_disk,) = AnnotateDiskParams(self.instance, [root], self.cfg)
3274     for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
3275       if self.op.keep_disks and disk.dev_type in constants.DT_EXT:
3276         continue
3277       self.cfg.SetDiskID(disk, node)
3278       msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
3279       if msg:
3280         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
3281                         " continuing anyway", idx, node, msg)
3282
3283     # if this is a DRBD disk, return its port to the pool
3284     if root.dev_type in constants.LDS_DRBD:
3285       self.cfg.AddTcpUdpPort(root.logical_id[2])
3286
3287     return hotmsg
3288
3289   def _CreateNewNic(self, idx, params, private):
3290     """Creates data structure for a new network interface.
3291
3292     """
3293     mac = params[constants.INIC_MAC]
3294     ip = params.get(constants.INIC_IP, None)
3295     net = params.get(constants.INIC_NETWORK, None)
3296     name = params.get(constants.INIC_NAME, None)
3297     net_uuid = self.cfg.LookupNetwork(net)
3298     #TODO: not private.filled?? can a nic have no nicparams??
3299     nicparams = private.filled
3300     nobj = objects.NIC(mac=mac, ip=ip, network=net_uuid, name=name,
3301                        nicparams=nicparams)
3302     nobj.uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
3303
3304     changes = [
3305       ("nic.%d" % idx,
3306        "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" %
3307        (mac, ip, private.filled[constants.NIC_MODE],
3308        private.filled[constants.NIC_LINK], net)),
3309       ]
3310
3311     if self.op.hotplug:
3312       msg = self._HotplugDevice(constants.HOTPLUG_ACTION_ADD,
3313                                 constants.HOTPLUG_TARGET_NIC,
3314                                 nobj, None, idx)
3315       changes.append(("nic.%d" % idx, msg))
3316
3317     return (nobj, changes)
3318
3319   def _ApplyNicMods(self, idx, nic, params, private):
3320     """Modifies a network interface.
3321
3322     """
3323     changes = []
3324
3325     for key in [constants.INIC_MAC, constants.INIC_IP, constants.INIC_NAME]:
3326       if key in params:
3327         changes.append(("nic.%s/%d" % (key, idx), params[key]))
3328         setattr(nic, key, params[key])
3329
3330     new_net = params.get(constants.INIC_NETWORK, nic.network)
3331     new_net_uuid = self.cfg.LookupNetwork(new_net)
3332     if new_net_uuid != nic.network:
3333       changes.append(("nic.network/%d" % idx, new_net))
3334       nic.network = new_net_uuid
3335
3336     if private.filled:
3337       nic.nicparams = private.filled
3338
3339       for (key, val) in nic.nicparams.items():
3340         changes.append(("nic.%s/%d" % (key, idx), val))
3341
3342     if self.op.hotplug:
3343       msg = self._HotplugDevice(constants.HOTPLUG_ACTION_MODIFY,
3344                                 constants.HOTPLUG_TARGET_NIC,
3345                                 nic, None, idx)
3346       changes.append(("nic/%d" % idx, msg))
3347
3348     return changes
3349
3350   def _RemoveNic(self, idx, nic, _):
3351     if self.op.hotplug:
3352       return self._HotplugDevice(constants.HOTPLUG_ACTION_REMOVE,
3353                                  constants.HOTPLUG_TARGET_NIC,
3354                                  nic, None, idx)
3355
3356   def Exec(self, feedback_fn):
3357     """Modifies an instance.
3358
3359     All parameters take effect only at the next restart of the instance.
3360
3361     """
3362     # Process here the warnings from CheckPrereq, as we don't have a
3363     # feedback_fn there.
3364     # TODO: Replace with self.LogWarning
3365     for warn in self.warn:
3366       feedback_fn("WARNING: %s" % warn)
3367
3368     assert ((self.op.disk_template is None) ^
3369             bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
3370       "Not owning any node resource locks"
3371
3372     result = []
3373     instance = self.instance
3374
3375     # New primary node
3376     if self.op.pnode:
3377       instance.primary_node = self.op.pnode
3378
3379     # runtime memory
3380     if self.op.runtime_mem:
3381       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
3382                                                      instance,
3383                                                      self.op.runtime_mem)
3384       rpcres.Raise("Cannot modify instance runtime memory")
3385       result.append(("runtime_memory", self.op.runtime_mem))
3386
3387     # Apply disk changes
3388     _ApplyContainerMods("disk", instance.disks, result, self.diskmod,
3389                         self._CreateNewDisk, self._ModifyDisk,
3390                         self._RemoveDisk)
3391     _UpdateIvNames(0, instance.disks)
3392
3393     if self.op.disk_template:
3394       if __debug__:
3395         check_nodes = set(instance.all_nodes)
3396         if self.op.remote_node:
3397           check_nodes.add(self.op.remote_node)
3398         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
3399           owned = self.owned_locks(level)
3400           assert not (check_nodes - owned), \
3401             ("Not owning the correct locks, owning %r, expected at least %r" %
3402              (owned, check_nodes))
3403
3404       r_shut = ShutdownInstanceDisks(self, instance)
3405       if not r_shut:
3406         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
3407                                  " proceed with disk template conversion")
3408       mode = (instance.disk_template, self.op.disk_template)
3409       try:
3410         self._DISK_CONVERSIONS[mode](self, feedback_fn)
3411       except:
3412         self.cfg.ReleaseDRBDMinors(instance.name)
3413         raise
3414       result.append(("disk_template", self.op.disk_template))
3415
3416       assert instance.disk_template == self.op.disk_template, \
3417         ("Expected disk template '%s', found '%s'" %
3418          (self.op.disk_template, instance.disk_template))
3419
3420     # Release node and resource locks if there are any (they might already have
3421     # been released during disk conversion)
3422     ReleaseLocks(self, locking.LEVEL_NODE)
3423     ReleaseLocks(self, locking.LEVEL_NODE_RES)
3424
3425     # Apply NIC changes
3426     if self._new_nics is not None:
3427       instance.nics = self._new_nics
3428       result.extend(self._nic_chgdesc)
3429
3430     # hvparams changes
3431     if self.op.hvparams:
3432       instance.hvparams = self.hv_inst
3433       for key, val in self.op.hvparams.iteritems():
3434         result.append(("hv/%s" % key, val))
3435
3436     # beparams changes
3437     if self.op.beparams:
3438       instance.beparams = self.be_inst
3439       for key, val in self.op.beparams.iteritems():
3440         result.append(("be/%s" % key, val))
3441
3442     # OS change
3443     if self.op.os_name:
3444       instance.os = self.op.os_name
3445
3446     # osparams changes
3447     if self.op.osparams:
3448       instance.osparams = self.os_inst
3449       for key, val in self.op.osparams.iteritems():
3450         result.append(("os/%s" % key, val))
3451
3452     if self.op.offline is None:
3453       # Ignore
3454       pass
3455     elif self.op.offline:
3456       # Mark instance as offline
3457       self.cfg.MarkInstanceOffline(instance.name)
3458       result.append(("admin_state", constants.ADMINST_OFFLINE))
3459     else:
3460       # Mark instance as online, but stopped
3461       self.cfg.MarkInstanceDown(instance.name)
3462       result.append(("admin_state", constants.ADMINST_DOWN))
3463
3464     self.cfg.Update(instance, feedback_fn, self.proc.GetECId())
3465
3466     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
3467                 self.owned_locks(locking.LEVEL_NODE)), \
3468       "All node locks should have been released by now"
3469
3470     return result
3471
3472   _DISK_CONVERSIONS = {
3473     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
3474     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
3475     }
3476
3477
3478 class LUInstanceChangeGroup(LogicalUnit):
3479   HPATH = "instance-change-group"
3480   HTYPE = constants.HTYPE_INSTANCE
3481   REQ_BGL = False
3482
3483   def ExpandNames(self):
3484     self.share_locks = ShareAll()
3485
3486     self.needed_locks = {
3487       locking.LEVEL_NODEGROUP: [],
3488       locking.LEVEL_NODE: [],
3489       locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
3490       }
3491
3492     self._ExpandAndLockInstance()
3493
3494     if self.op.target_groups:
3495       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
3496                                   self.op.target_groups)
3497     else:
3498       self.req_target_uuids = None
3499
3500     self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
3501
3502   def DeclareLocks(self, level):
3503     if level == locking.LEVEL_NODEGROUP:
3504       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3505
3506       if self.req_target_uuids:
3507         lock_groups = set(self.req_target_uuids)
3508
3509         # Lock all groups used by instance optimistically; this requires going
3510         # via the node before it's locked, requiring verification later on
3511         instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
3512         lock_groups.update(instance_groups)
3513       else:
3514         # No target groups, need to lock all of them
3515         lock_groups = locking.ALL_SET
3516
3517       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
3518
3519     elif level == locking.LEVEL_NODE:
3520       if self.req_target_uuids:
3521         # Lock all nodes used by instances
3522         self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3523         self._LockInstancesNodes()
3524
3525         # Lock all nodes in all potential target groups
3526         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
3527                        self.cfg.GetInstanceNodeGroups(self.op.instance_name))
3528         member_nodes = [node_name
3529                         for group in lock_groups
3530                         for node_name in self.cfg.GetNodeGroup(group).members]
3531         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3532       else:
3533         # Lock all nodes as all groups are potential targets
3534         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3535
3536   def CheckPrereq(self):
3537     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3538     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3539     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3540
3541     assert (self.req_target_uuids is None or
3542             owned_groups.issuperset(self.req_target_uuids))
3543     assert owned_instances == set([self.op.instance_name])
3544
3545     # Get instance information
3546     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3547
3548     # Check if node groups for locked instance are still correct
3549     assert owned_nodes.issuperset(self.instance.all_nodes), \
3550       ("Instance %s's nodes changed while we kept the lock" %
3551        self.op.instance_name)
3552
3553     inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
3554                                           owned_groups)
3555
3556     if self.req_target_uuids:
3557       # User requested specific target groups
3558       self.target_uuids = frozenset(self.req_target_uuids)
3559     else:
3560       # All groups except those used by the instance are potential targets
3561       self.target_uuids = owned_groups - inst_groups
3562
3563     conflicting_groups = self.target_uuids & inst_groups
3564     if conflicting_groups:
3565       raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
3566                                  " used by the instance '%s'" %
3567                                  (utils.CommaJoin(conflicting_groups),
3568                                   self.op.instance_name),
3569                                  errors.ECODE_INVAL)
3570
3571     if not self.target_uuids:
3572       raise errors.OpPrereqError("There are no possible target groups",
3573                                  errors.ECODE_INVAL)
3574
3575   def BuildHooksEnv(self):
3576     """Build hooks env.
3577
3578     """
3579     assert self.target_uuids
3580
3581     env = {
3582       "TARGET_GROUPS": " ".join(self.target_uuids),
3583       }
3584
3585     env.update(BuildInstanceHookEnvByObject(self, self.instance))
3586
3587     return env
3588
3589   def BuildHooksNodes(self):
3590     """Build hooks nodes.
3591
3592     """
3593     mn = self.cfg.GetMasterNode()
3594     return ([mn], [mn])
3595
3596   def Exec(self, feedback_fn):
3597     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
3598
3599     assert instances == [self.op.instance_name], "Instance not locked"
3600
3601     req = iallocator.IAReqGroupChange(instances=instances,
3602                                       target_groups=list(self.target_uuids))
3603     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
3604
3605     ial.Run(self.op.iallocator)
3606
3607     if not ial.success:
3608       raise errors.OpPrereqError("Can't compute solution for changing group of"
3609                                  " instance '%s' using iallocator '%s': %s" %
3610                                  (self.op.instance_name, self.op.iallocator,
3611                                   ial.info), errors.ECODE_NORES)
3612
3613     jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
3614
3615     self.LogInfo("Iallocator returned %s job(s) for changing group of"
3616                  " instance '%s'", len(jobs), self.op.instance_name)
3617
3618     return ResultWithJobs(jobs)