Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance.py @ 125e1230

History | View | Annotate | Download (134.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Logical units dealing with instances."""
23

    
24
import OpenSSL
25
import copy
26
import logging
27
import os
28

    
29
from ganeti import compat
30
from ganeti import constants
31
from ganeti import errors
32
from ganeti import ht
33
from ganeti import hypervisor
34
from ganeti import locking
35
from ganeti.masterd import iallocator
36
from ganeti import masterd
37
from ganeti import netutils
38
from ganeti import objects
39
from ganeti import opcodes
40
from ganeti import pathutils
41
from ganeti import rpc
42
from ganeti import utils
43

    
44
from ganeti.cmdlib.base import NoHooksLU, LogicalUnit, ResultWithJobs
45

    
46
from ganeti.cmdlib.common import INSTANCE_DOWN, \
47
  INSTANCE_NOT_RUNNING, CAN_CHANGE_INSTANCE_OFFLINE, CheckNodeOnline, \
48
  ShareAll, GetDefaultIAllocator, CheckInstanceNodeGroups, \
49
  LoadNodeEvacResult, CheckIAllocatorOrNode, CheckParamsNotGlobal, \
50
  IsExclusiveStorageEnabledNode, CheckHVParams, CheckOSParams, \
51
  AnnotateDiskParams, GetUpdatedParams, ExpandInstanceName, \
52
  ComputeIPolicySpecViolation, CheckInstanceState, ExpandNodeName
53
from ganeti.cmdlib.instance_storage import CreateDisks, \
54
  CheckNodesFreeDiskPerVG, WipeDisks, WipeOrCleanupDisks, WaitForSync, \
55
  IsExclusiveStorageEnabledNodeName, CreateSingleBlockDev, ComputeDisks, \
56
  CheckRADOSFreeSpace, ComputeDiskSizePerVG, GenerateDiskTemplate, \
57
  StartInstanceDisks, ShutdownInstanceDisks, AssembleInstanceDisks
58
from ganeti.cmdlib.instance_utils import BuildInstanceHookEnvByObject, \
59
  GetClusterDomainSecret, BuildInstanceHookEnv, NICListToTuple, \
60
  NICToTuple, CheckNodeNotDrained, RemoveInstance, CopyLockList, \
61
  ReleaseLocks, CheckNodeVmCapable, CheckTargetNodeIPolicy, \
62
  GetInstanceInfoText, RemoveDisks, CheckNodeFreeMemory, \
63
  CheckInstanceBridgesExist, CheckNicsBridgesExist, CheckNodeHasOS
64

    
65
import ganeti.masterd.instance
66

    
67

    
68
#: Type description for changes as returned by L{_ApplyContainerMods}'s
69
#: callbacks
70
_TApplyContModsCbChanges = \
71
  ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
72
    ht.TNonEmptyString,
73
    ht.TAny,
74
    ])))
75

    
76

    
77
def _CheckHostnameSane(lu, name):
78
  """Ensures that a given hostname resolves to a 'sane' name.
79

80
  The given name is required to be a prefix of the resolved hostname,
81
  to prevent accidental mismatches.
82

83
  @param lu: the logical unit on behalf of which we're checking
84
  @param name: the name we should resolve and check
85
  @return: the resolved hostname object
86

87
  """
88
  hostname = netutils.GetHostname(name=name)
89
  if hostname.name != name:
90
    lu.LogInfo("Resolved given name '%s' to '%s'", name, hostname.name)
91
  if not utils.MatchNameComponent(name, [hostname.name]):
92
    raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
93
                                " same as given hostname '%s'") %
94
                               (hostname.name, name), errors.ECODE_INVAL)
95
  return hostname
96

    
97

    
98
def _CheckOpportunisticLocking(op):
99
  """Generate error if opportunistic locking is not possible.
100

101
  """
102
  if op.opportunistic_locking and not op.iallocator:
103
    raise errors.OpPrereqError("Opportunistic locking is only available in"
104
                               " combination with an instance allocator",
105
                               errors.ECODE_INVAL)
106

    
107

    
108
def _CreateInstanceAllocRequest(op, disks, nics, beparams, node_whitelist):
109
  """Wrapper around IAReqInstanceAlloc.
110

111
  @param op: The instance opcode
112
  @param disks: The computed disks
113
  @param nics: The computed nics
114
  @param beparams: The full filled beparams
115
  @param node_whitelist: List of nodes which should appear as online to the
116
    allocator (unless the node is already marked offline)
117

118
  @returns: A filled L{iallocator.IAReqInstanceAlloc}
119

120
  """
121
  spindle_use = beparams[constants.BE_SPINDLE_USE]
122
  return iallocator.IAReqInstanceAlloc(name=op.instance_name,
123
                                       disk_template=op.disk_template,
124
                                       tags=op.tags,
125
                                       os=op.os_type,
126
                                       vcpus=beparams[constants.BE_VCPUS],
127
                                       memory=beparams[constants.BE_MAXMEM],
128
                                       spindle_use=spindle_use,
129
                                       disks=disks,
130
                                       nics=[n.ToDict() for n in nics],
131
                                       hypervisor=op.hypervisor,
132
                                       node_whitelist=node_whitelist)
133

    
134

    
135
def _ComputeFullBeParams(op, cluster):
136
  """Computes the full beparams.
137

138
  @param op: The instance opcode
139
  @param cluster: The cluster config object
140

141
  @return: The fully filled beparams
142

143
  """
144
  default_beparams = cluster.beparams[constants.PP_DEFAULT]
145
  for param, value in op.beparams.iteritems():
146
    if value == constants.VALUE_AUTO:
147
      op.beparams[param] = default_beparams[param]
148
  objects.UpgradeBeParams(op.beparams)
149
  utils.ForceDictType(op.beparams, constants.BES_PARAMETER_TYPES)
150
  return cluster.SimpleFillBE(op.beparams)
151

    
152

    
153
def _ComputeNics(op, cluster, default_ip, cfg, ec_id):
154
  """Computes the nics.
155

156
  @param op: The instance opcode
157
  @param cluster: Cluster configuration object
158
  @param default_ip: The default ip to assign
159
  @param cfg: An instance of the configuration object
160
  @param ec_id: Execution context ID
161

162
  @returns: The build up nics
163

164
  """
165
  nics = []
166
  for nic in op.nics:
167
    nic_mode_req = nic.get(constants.INIC_MODE, None)
168
    nic_mode = nic_mode_req
169
    if nic_mode is None or nic_mode == constants.VALUE_AUTO:
170
      nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
171

    
172
    net = nic.get(constants.INIC_NETWORK, None)
173
    link = nic.get(constants.NIC_LINK, None)
174
    ip = nic.get(constants.INIC_IP, None)
175

    
176
    if net is None or net.lower() == constants.VALUE_NONE:
177
      net = None
178
    else:
179
      if nic_mode_req is not None or link is not None:
180
        raise errors.OpPrereqError("If network is given, no mode or link"
181
                                   " is allowed to be passed",
182
                                   errors.ECODE_INVAL)
183

    
184
    # ip validity checks
185
    if ip is None or ip.lower() == constants.VALUE_NONE:
186
      nic_ip = None
187
    elif ip.lower() == constants.VALUE_AUTO:
188
      if not op.name_check:
189
        raise errors.OpPrereqError("IP address set to auto but name checks"
190
                                   " have been skipped",
191
                                   errors.ECODE_INVAL)
192
      nic_ip = default_ip
193
    else:
194
      # We defer pool operations until later, so that the iallocator has
195
      # filled in the instance's node(s) dimara
196
      if ip.lower() == constants.NIC_IP_POOL:
197
        if net is None:
198
          raise errors.OpPrereqError("if ip=pool, parameter network"
199
                                     " must be passed too",
200
                                     errors.ECODE_INVAL)
201

    
202
      elif not netutils.IPAddress.IsValid(ip):
203
        raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
204
                                   errors.ECODE_INVAL)
205

    
206
      nic_ip = ip
207

    
208
    # TODO: check the ip address for uniqueness
209
    if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
210
      raise errors.OpPrereqError("Routed nic mode requires an ip address",
211
                                 errors.ECODE_INVAL)
212

    
213
    # MAC address verification
214
    mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
215
    if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
216
      mac = utils.NormalizeAndValidateMac(mac)
217

    
218
      try:
219
        # TODO: We need to factor this out
220
        cfg.ReserveMAC(mac, ec_id)
221
      except errors.ReservationError:
222
        raise errors.OpPrereqError("MAC address %s already in use"
223
                                   " in cluster" % mac,
224
                                   errors.ECODE_NOTUNIQUE)
225

    
226
    #  Build nic parameters
227
    nicparams = {}
228
    if nic_mode_req:
229
      nicparams[constants.NIC_MODE] = nic_mode
230
    if link:
231
      nicparams[constants.NIC_LINK] = link
232

    
233
    check_params = cluster.SimpleFillNIC(nicparams)
234
    objects.NIC.CheckParameterSyntax(check_params)
235
    net_uuid = cfg.LookupNetwork(net)
236
    name = nic.get(constants.INIC_NAME, None)
237
    if name is not None and name.lower() == constants.VALUE_NONE:
238
      name = None
239
    nic_obj = objects.NIC(mac=mac, ip=nic_ip, name=name,
240
                          network=net_uuid, nicparams=nicparams)
241
    nic_obj.uuid = cfg.GenerateUniqueID(ec_id)
242
    nics.append(nic_obj)
243

    
244
  return nics
245

    
246

    
247
def _CheckForConflictingIp(lu, ip, node):
248
  """In case of conflicting IP address raise error.
249

250
  @type ip: string
251
  @param ip: IP address
252
  @type node: string
253
  @param node: node name
254

255
  """
256
  (conf_net, _) = lu.cfg.CheckIPInNodeGroup(ip, node)
257
  if conf_net is not None:
258
    raise errors.OpPrereqError(("The requested IP address (%s) belongs to"
259
                                " network %s, but the target NIC does not." %
260
                                (ip, conf_net)),
261
                               errors.ECODE_STATE)
262

    
263
  return (None, None)
264

    
265

    
266
def _ComputeIPolicyInstanceSpecViolation(
267
  ipolicy, instance_spec, disk_template,
268
  _compute_fn=ComputeIPolicySpecViolation):
269
  """Compute if instance specs meets the specs of ipolicy.
270

271
  @type ipolicy: dict
272
  @param ipolicy: The ipolicy to verify against
273
  @param instance_spec: dict
274
  @param instance_spec: The instance spec to verify
275
  @type disk_template: string
276
  @param disk_template: the disk template of the instance
277
  @param _compute_fn: The function to verify ipolicy (unittest only)
278
  @see: L{ComputeIPolicySpecViolation}
279

280
  """
281
  mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
282
  cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
283
  disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
284
  disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
285
  nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
286
  spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
287

    
288
  return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
289
                     disk_sizes, spindle_use, disk_template)
290

    
291

    
292
def _CheckOSVariant(os_obj, name):
293
  """Check whether an OS name conforms to the os variants specification.
294

295
  @type os_obj: L{objects.OS}
296
  @param os_obj: OS object to check
297
  @type name: string
298
  @param name: OS name passed by the user, to check for validity
299

300
  """
301
  variant = objects.OS.GetVariant(name)
302
  if not os_obj.supported_variants:
303
    if variant:
304
      raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
305
                                 " passed)" % (os_obj.name, variant),
306
                                 errors.ECODE_INVAL)
307
    return
308
  if not variant:
309
    raise errors.OpPrereqError("OS name must include a variant",
310
                               errors.ECODE_INVAL)
311

    
312
  if variant not in os_obj.supported_variants:
313
    raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
314

    
315

    
316
class LUInstanceCreate(LogicalUnit):
317
  """Create an instance.
318

319
  """
320
  HPATH = "instance-add"
321
  HTYPE = constants.HTYPE_INSTANCE
322
  REQ_BGL = False
323

    
324
  def CheckArguments(self):
325
    """Check arguments.
326

327
    """
328
    # do not require name_check to ease forward/backward compatibility
329
    # for tools
330
    if self.op.no_install and self.op.start:
331
      self.LogInfo("No-installation mode selected, disabling startup")
332
      self.op.start = False
333
    # validate/normalize the instance name
334
    self.op.instance_name = \
335
      netutils.Hostname.GetNormalizedName(self.op.instance_name)
336

    
337
    if self.op.ip_check and not self.op.name_check:
338
      # TODO: make the ip check more flexible and not depend on the name check
339
      raise errors.OpPrereqError("Cannot do IP address check without a name"
340
                                 " check", errors.ECODE_INVAL)
341

    
342
    # check nics' parameter names
343
    for nic in self.op.nics:
344
      utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
345
    # check that NIC's parameters names are unique and valid
346
    utils.ValidateDeviceNames("NIC", self.op.nics)
347

    
348
    # check that disk's names are unique and valid
349
    utils.ValidateDeviceNames("disk", self.op.disks)
350

    
351
    cluster = self.cfg.GetClusterInfo()
352
    if not self.op.disk_template in cluster.enabled_disk_templates:
353
      raise errors.OpPrereqError("Cannot create an instance with disk template"
354
                                 " '%s', because it is not enabled in the"
355
                                 " cluster. Enabled disk templates are: %s." %
356
                                 (self.op.disk_template,
357
                                  ",".join(cluster.enabled_disk_templates)))
358

    
359
    # check disks. parameter names and consistent adopt/no-adopt strategy
360
    has_adopt = has_no_adopt = False
361
    for disk in self.op.disks:
362
      if self.op.disk_template != constants.DT_EXT:
363
        utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
364
      if constants.IDISK_ADOPT in disk:
365
        has_adopt = True
366
      else:
367
        has_no_adopt = True
368
    if has_adopt and has_no_adopt:
369
      raise errors.OpPrereqError("Either all disks are adopted or none is",
370
                                 errors.ECODE_INVAL)
371
    if has_adopt:
372
      if self.op.disk_template not in constants.DTS_MAY_ADOPT:
373
        raise errors.OpPrereqError("Disk adoption is not supported for the"
374
                                   " '%s' disk template" %
375
                                   self.op.disk_template,
376
                                   errors.ECODE_INVAL)
377
      if self.op.iallocator is not None:
378
        raise errors.OpPrereqError("Disk adoption not allowed with an"
379
                                   " iallocator script", errors.ECODE_INVAL)
380
      if self.op.mode == constants.INSTANCE_IMPORT:
381
        raise errors.OpPrereqError("Disk adoption not allowed for"
382
                                   " instance import", errors.ECODE_INVAL)
383
    else:
384
      if self.op.disk_template in constants.DTS_MUST_ADOPT:
385
        raise errors.OpPrereqError("Disk template %s requires disk adoption,"
386
                                   " but no 'adopt' parameter given" %
387
                                   self.op.disk_template,
388
                                   errors.ECODE_INVAL)
389

    
390
    self.adopt_disks = has_adopt
391

    
392
    # instance name verification
393
    if self.op.name_check:
394
      self.hostname1 = _CheckHostnameSane(self, self.op.instance_name)
395
      self.op.instance_name = self.hostname1.name
396
      # used in CheckPrereq for ip ping check
397
      self.check_ip = self.hostname1.ip
398
    else:
399
      self.check_ip = None
400

    
401
    # file storage checks
402
    if (self.op.file_driver and
403
        not self.op.file_driver in constants.FILE_DRIVER):
404
      raise errors.OpPrereqError("Invalid file driver name '%s'" %
405
                                 self.op.file_driver, errors.ECODE_INVAL)
406

    
407
    if self.op.disk_template == constants.DT_FILE:
408
      opcodes.RequireFileStorage()
409
    elif self.op.disk_template == constants.DT_SHARED_FILE:
410
      opcodes.RequireSharedFileStorage()
411

    
412
    ### Node/iallocator related checks
413
    CheckIAllocatorOrNode(self, "iallocator", "pnode")
414

    
415
    if self.op.pnode is not None:
416
      if self.op.disk_template in constants.DTS_INT_MIRROR:
417
        if self.op.snode is None:
418
          raise errors.OpPrereqError("The networked disk templates need"
419
                                     " a mirror node", errors.ECODE_INVAL)
420
      elif self.op.snode:
421
        self.LogWarning("Secondary node will be ignored on non-mirrored disk"
422
                        " template")
423
        self.op.snode = None
424

    
425
    _CheckOpportunisticLocking(self.op)
426

    
427
    self._cds = GetClusterDomainSecret()
428

    
429
    if self.op.mode == constants.INSTANCE_IMPORT:
430
      # On import force_variant must be True, because if we forced it at
431
      # initial install, our only chance when importing it back is that it
432
      # works again!
433
      self.op.force_variant = True
434

    
435
      if self.op.no_install:
436
        self.LogInfo("No-installation mode has no effect during import")
437

    
438
    elif self.op.mode == constants.INSTANCE_CREATE:
439
      if self.op.os_type is None:
440
        raise errors.OpPrereqError("No guest OS specified",
441
                                   errors.ECODE_INVAL)
442
      if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
443
        raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
444
                                   " installation" % self.op.os_type,
445
                                   errors.ECODE_STATE)
446
      if self.op.disk_template is None:
447
        raise errors.OpPrereqError("No disk template specified",
448
                                   errors.ECODE_INVAL)
449

    
450
    elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
451
      # Check handshake to ensure both clusters have the same domain secret
452
      src_handshake = self.op.source_handshake
453
      if not src_handshake:
454
        raise errors.OpPrereqError("Missing source handshake",
455
                                   errors.ECODE_INVAL)
456

    
457
      errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
458
                                                           src_handshake)
459
      if errmsg:
460
        raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
461
                                   errors.ECODE_INVAL)
462

    
463
      # Load and check source CA
464
      self.source_x509_ca_pem = self.op.source_x509_ca
465
      if not self.source_x509_ca_pem:
466
        raise errors.OpPrereqError("Missing source X509 CA",
467
                                   errors.ECODE_INVAL)
468

    
469
      try:
470
        (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
471
                                                    self._cds)
472
      except OpenSSL.crypto.Error, err:
473
        raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
474
                                   (err, ), errors.ECODE_INVAL)
475

    
476
      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
477
      if errcode is not None:
478
        raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
479
                                   errors.ECODE_INVAL)
480

    
481
      self.source_x509_ca = cert
482

    
483
      src_instance_name = self.op.source_instance_name
484
      if not src_instance_name:
485
        raise errors.OpPrereqError("Missing source instance name",
486
                                   errors.ECODE_INVAL)
487

    
488
      self.source_instance_name = \
489
        netutils.GetHostname(name=src_instance_name).name
490

    
491
    else:
492
      raise errors.OpPrereqError("Invalid instance creation mode %r" %
493
                                 self.op.mode, errors.ECODE_INVAL)
494

    
495
  def ExpandNames(self):
496
    """ExpandNames for CreateInstance.
497

498
    Figure out the right locks for instance creation.
499

500
    """
501
    self.needed_locks = {}
502

    
503
    instance_name = self.op.instance_name
504
    # this is just a preventive check, but someone might still add this
505
    # instance in the meantime, and creation will fail at lock-add time
506
    if instance_name in self.cfg.GetInstanceList():
507
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
508
                                 instance_name, errors.ECODE_EXISTS)
509

    
510
    self.add_locks[locking.LEVEL_INSTANCE] = instance_name
511

    
512
    if self.op.iallocator:
513
      # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
514
      # specifying a group on instance creation and then selecting nodes from
515
      # that group
516
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
517
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
518

    
519
      if self.op.opportunistic_locking:
520
        self.opportunistic_locks[locking.LEVEL_NODE] = True
521
        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
522
    else:
523
      self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
524
      nodelist = [self.op.pnode]
525
      if self.op.snode is not None:
526
        self.op.snode = ExpandNodeName(self.cfg, self.op.snode)
527
        nodelist.append(self.op.snode)
528
      self.needed_locks[locking.LEVEL_NODE] = nodelist
529

    
530
    # in case of import lock the source node too
531
    if self.op.mode == constants.INSTANCE_IMPORT:
532
      src_node = self.op.src_node
533
      src_path = self.op.src_path
534

    
535
      if src_path is None:
536
        self.op.src_path = src_path = self.op.instance_name
537

    
538
      if src_node is None:
539
        self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
540
        self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
541
        self.op.src_node = None
542
        if os.path.isabs(src_path):
543
          raise errors.OpPrereqError("Importing an instance from a path"
544
                                     " requires a source node option",
545
                                     errors.ECODE_INVAL)
546
      else:
547
        self.op.src_node = src_node = ExpandNodeName(self.cfg, src_node)
548
        if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
549
          self.needed_locks[locking.LEVEL_NODE].append(src_node)
550
        if not os.path.isabs(src_path):
551
          self.op.src_path = src_path = \
552
            utils.PathJoin(pathutils.EXPORT_DIR, src_path)
553

    
554
    self.needed_locks[locking.LEVEL_NODE_RES] = \
555
      CopyLockList(self.needed_locks[locking.LEVEL_NODE])
556

    
557
  def _RunAllocator(self):
558
    """Run the allocator based on input opcode.
559

560
    """
561
    if self.op.opportunistic_locking:
562
      # Only consider nodes for which a lock is held
563
      node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
564
    else:
565
      node_whitelist = None
566

    
567
    #TODO Export network to iallocator so that it chooses a pnode
568
    #     in a nodegroup that has the desired network connected to
569
    req = _CreateInstanceAllocRequest(self.op, self.disks,
570
                                      self.nics, self.be_full,
571
                                      node_whitelist)
572
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
573

    
574
    ial.Run(self.op.iallocator)
575

    
576
    if not ial.success:
577
      # When opportunistic locks are used only a temporary failure is generated
578
      if self.op.opportunistic_locking:
579
        ecode = errors.ECODE_TEMP_NORES
580
      else:
581
        ecode = errors.ECODE_NORES
582

    
583
      raise errors.OpPrereqError("Can't compute nodes using"
584
                                 " iallocator '%s': %s" %
585
                                 (self.op.iallocator, ial.info),
586
                                 ecode)
587

    
588
    self.op.pnode = ial.result[0]
589
    self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
590
                 self.op.instance_name, self.op.iallocator,
591
                 utils.CommaJoin(ial.result))
592

    
593
    assert req.RequiredNodes() in (1, 2), "Wrong node count from iallocator"
594

    
595
    if req.RequiredNodes() == 2:
596
      self.op.snode = ial.result[1]
597

    
598
  def BuildHooksEnv(self):
599
    """Build hooks env.
600

601
    This runs on master, primary and secondary nodes of the instance.
602

603
    """
604
    env = {
605
      "ADD_MODE": self.op.mode,
606
      }
607
    if self.op.mode == constants.INSTANCE_IMPORT:
608
      env["SRC_NODE"] = self.op.src_node
609
      env["SRC_PATH"] = self.op.src_path
610
      env["SRC_IMAGES"] = self.src_images
611

    
612
    env.update(BuildInstanceHookEnv(
613
      name=self.op.instance_name,
614
      primary_node=self.op.pnode,
615
      secondary_nodes=self.secondaries,
616
      status=self.op.start,
617
      os_type=self.op.os_type,
618
      minmem=self.be_full[constants.BE_MINMEM],
619
      maxmem=self.be_full[constants.BE_MAXMEM],
620
      vcpus=self.be_full[constants.BE_VCPUS],
621
      nics=NICListToTuple(self, self.nics),
622
      disk_template=self.op.disk_template,
623
      disks=[(d[constants.IDISK_NAME], d.get("uuid", ""),
624
              d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
625
             for d in self.disks],
626
      bep=self.be_full,
627
      hvp=self.hv_full,
628
      hypervisor_name=self.op.hypervisor,
629
      tags=self.op.tags,
630
      ))
631

    
632
    return env
633

    
634
  def BuildHooksNodes(self):
635
    """Build hooks nodes.
636

637
    """
638
    nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
639
    return nl, nl
640

    
641
  def _ReadExportInfo(self):
642
    """Reads the export information from disk.
643

644
    It will override the opcode source node and path with the actual
645
    information, if these two were not specified before.
646

647
    @return: the export information
648

649
    """
650
    assert self.op.mode == constants.INSTANCE_IMPORT
651

    
652
    src_node = self.op.src_node
653
    src_path = self.op.src_path
654

    
655
    if src_node is None:
656
      locked_nodes = self.owned_locks(locking.LEVEL_NODE)
657
      exp_list = self.rpc.call_export_list(locked_nodes)
658
      found = False
659
      for node in exp_list:
660
        if exp_list[node].fail_msg:
661
          continue
662
        if src_path in exp_list[node].payload:
663
          found = True
664
          self.op.src_node = src_node = node
665
          self.op.src_path = src_path = utils.PathJoin(pathutils.EXPORT_DIR,
666
                                                       src_path)
667
          break
668
      if not found:
669
        raise errors.OpPrereqError("No export found for relative path %s" %
670
                                   src_path, errors.ECODE_INVAL)
671

    
672
    CheckNodeOnline(self, src_node)
673
    result = self.rpc.call_export_info(src_node, src_path)
674
    result.Raise("No export or invalid export found in dir %s" % src_path)
675

    
676
    export_info = objects.SerializableConfigParser.Loads(str(result.payload))
677
    if not export_info.has_section(constants.INISECT_EXP):
678
      raise errors.ProgrammerError("Corrupted export config",
679
                                   errors.ECODE_ENVIRON)
680

    
681
    ei_version = export_info.get(constants.INISECT_EXP, "version")
682
    if (int(ei_version) != constants.EXPORT_VERSION):
683
      raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
684
                                 (ei_version, constants.EXPORT_VERSION),
685
                                 errors.ECODE_ENVIRON)
686
    return export_info
687

    
688
  def _ReadExportParams(self, einfo):
689
    """Use export parameters as defaults.
690

691
    In case the opcode doesn't specify (as in override) some instance
692
    parameters, then try to use them from the export information, if
693
    that declares them.
694

695
    """
696
    self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
697

    
698
    if self.op.disk_template is None:
699
      if einfo.has_option(constants.INISECT_INS, "disk_template"):
700
        self.op.disk_template = einfo.get(constants.INISECT_INS,
701
                                          "disk_template")
702
        if self.op.disk_template not in constants.DISK_TEMPLATES:
703
          raise errors.OpPrereqError("Disk template specified in configuration"
704
                                     " file is not one of the allowed values:"
705
                                     " %s" %
706
                                     " ".join(constants.DISK_TEMPLATES),
707
                                     errors.ECODE_INVAL)
708
      else:
709
        raise errors.OpPrereqError("No disk template specified and the export"
710
                                   " is missing the disk_template information",
711
                                   errors.ECODE_INVAL)
712

    
713
    if not self.op.disks:
714
      disks = []
715
      # TODO: import the disk iv_name too
716
      for idx in range(constants.MAX_DISKS):
717
        if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
718
          disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
719
          disks.append({constants.IDISK_SIZE: disk_sz})
720
      self.op.disks = disks
721
      if not disks and self.op.disk_template != constants.DT_DISKLESS:
722
        raise errors.OpPrereqError("No disk info specified and the export"
723
                                   " is missing the disk information",
724
                                   errors.ECODE_INVAL)
725

    
726
    if not self.op.nics:
727
      nics = []
728
      for idx in range(constants.MAX_NICS):
729
        if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
730
          ndict = {}
731
          for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
732
            v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
733
            ndict[name] = v
734
          nics.append(ndict)
735
        else:
736
          break
737
      self.op.nics = nics
738

    
739
    if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
740
      self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
741

    
742
    if (self.op.hypervisor is None and
743
        einfo.has_option(constants.INISECT_INS, "hypervisor")):
744
      self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
745

    
746
    if einfo.has_section(constants.INISECT_HYP):
747
      # use the export parameters but do not override the ones
748
      # specified by the user
749
      for name, value in einfo.items(constants.INISECT_HYP):
750
        if name not in self.op.hvparams:
751
          self.op.hvparams[name] = value
752

    
753
    if einfo.has_section(constants.INISECT_BEP):
754
      # use the parameters, without overriding
755
      for name, value in einfo.items(constants.INISECT_BEP):
756
        if name not in self.op.beparams:
757
          self.op.beparams[name] = value
758
        # Compatibility for the old "memory" be param
759
        if name == constants.BE_MEMORY:
760
          if constants.BE_MAXMEM not in self.op.beparams:
761
            self.op.beparams[constants.BE_MAXMEM] = value
762
          if constants.BE_MINMEM not in self.op.beparams:
763
            self.op.beparams[constants.BE_MINMEM] = value
764
    else:
765
      # try to read the parameters old style, from the main section
766
      for name in constants.BES_PARAMETERS:
767
        if (name not in self.op.beparams and
768
            einfo.has_option(constants.INISECT_INS, name)):
769
          self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
770

    
771
    if einfo.has_section(constants.INISECT_OSP):
772
      # use the parameters, without overriding
773
      for name, value in einfo.items(constants.INISECT_OSP):
774
        if name not in self.op.osparams:
775
          self.op.osparams[name] = value
776

    
777
  def _RevertToDefaults(self, cluster):
778
    """Revert the instance parameters to the default values.
779

780
    """
781
    # hvparams
782
    hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
783
    for name in self.op.hvparams.keys():
784
      if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
785
        del self.op.hvparams[name]
786
    # beparams
787
    be_defs = cluster.SimpleFillBE({})
788
    for name in self.op.beparams.keys():
789
      if name in be_defs and be_defs[name] == self.op.beparams[name]:
790
        del self.op.beparams[name]
791
    # nic params
792
    nic_defs = cluster.SimpleFillNIC({})
793
    for nic in self.op.nics:
794
      for name in constants.NICS_PARAMETERS:
795
        if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
796
          del nic[name]
797
    # osparams
798
    os_defs = cluster.SimpleFillOS(self.op.os_type, {})
799
    for name in self.op.osparams.keys():
800
      if name in os_defs and os_defs[name] == self.op.osparams[name]:
801
        del self.op.osparams[name]
802

    
803
  def _CalculateFileStorageDir(self):
804
    """Calculate final instance file storage dir.
805

806
    """
807
    # file storage dir calculation/check
808
    self.instance_file_storage_dir = None
809
    if self.op.disk_template in constants.DTS_FILEBASED:
810
      # build the full file storage dir path
811
      joinargs = []
812

    
813
      if self.op.disk_template == constants.DT_SHARED_FILE:
814
        get_fsd_fn = self.cfg.GetSharedFileStorageDir
815
      else:
816
        get_fsd_fn = self.cfg.GetFileStorageDir
817

    
818
      cfg_storagedir = get_fsd_fn()
819
      if not cfg_storagedir:
820
        raise errors.OpPrereqError("Cluster file storage dir not defined",
821
                                   errors.ECODE_STATE)
822
      joinargs.append(cfg_storagedir)
823

    
824
      if self.op.file_storage_dir is not None:
825
        joinargs.append(self.op.file_storage_dir)
826

    
827
      joinargs.append(self.op.instance_name)
828

    
829
      # pylint: disable=W0142
830
      self.instance_file_storage_dir = utils.PathJoin(*joinargs)
831

    
832
  def CheckPrereq(self): # pylint: disable=R0914
833
    """Check prerequisites.
834

835
    """
836
    self._CalculateFileStorageDir()
837

    
838
    if self.op.mode == constants.INSTANCE_IMPORT:
839
      export_info = self._ReadExportInfo()
840
      self._ReadExportParams(export_info)
841
      self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
842
    else:
843
      self._old_instance_name = None
844

    
845
    if (not self.cfg.GetVGName() and
846
        self.op.disk_template not in constants.DTS_NOT_LVM):
847
      raise errors.OpPrereqError("Cluster does not support lvm-based"
848
                                 " instances", errors.ECODE_STATE)
849

    
850
    if (self.op.hypervisor is None or
851
        self.op.hypervisor == constants.VALUE_AUTO):
852
      self.op.hypervisor = self.cfg.GetHypervisorType()
853

    
854
    cluster = self.cfg.GetClusterInfo()
855
    enabled_hvs = cluster.enabled_hypervisors
856
    if self.op.hypervisor not in enabled_hvs:
857
      raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
858
                                 " cluster (%s)" %
859
                                 (self.op.hypervisor, ",".join(enabled_hvs)),
860
                                 errors.ECODE_STATE)
861

    
862
    # Check tag validity
863
    for tag in self.op.tags:
864
      objects.TaggableObject.ValidateTag(tag)
865

    
866
    # check hypervisor parameter syntax (locally)
867
    utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
868
    filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
869
                                      self.op.hvparams)
870
    hv_type = hypervisor.GetHypervisorClass(self.op.hypervisor)
871
    hv_type.CheckParameterSyntax(filled_hvp)
872
    self.hv_full = filled_hvp
873
    # check that we don't specify global parameters on an instance
874
    CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, "hypervisor",
875
                         "instance", "cluster")
876

    
877
    # fill and remember the beparams dict
878
    self.be_full = _ComputeFullBeParams(self.op, cluster)
879

    
880
    # build os parameters
881
    self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
882

    
883
    # now that hvp/bep are in final format, let's reset to defaults,
884
    # if told to do so
885
    if self.op.identify_defaults:
886
      self._RevertToDefaults(cluster)
887

    
888
    # NIC buildup
889
    self.nics = _ComputeNics(self.op, cluster, self.check_ip, self.cfg,
890
                             self.proc.GetECId())
891

    
892
    # disk checks/pre-build
893
    default_vg = self.cfg.GetVGName()
894
    self.disks = ComputeDisks(self.op, default_vg)
895

    
896
    if self.op.mode == constants.INSTANCE_IMPORT:
897
      disk_images = []
898
      for idx in range(len(self.disks)):
899
        option = "disk%d_dump" % idx
900
        if export_info.has_option(constants.INISECT_INS, option):
901
          # FIXME: are the old os-es, disk sizes, etc. useful?
902
          export_name = export_info.get(constants.INISECT_INS, option)
903
          image = utils.PathJoin(self.op.src_path, export_name)
904
          disk_images.append(image)
905
        else:
906
          disk_images.append(False)
907

    
908
      self.src_images = disk_images
909

    
910
      if self.op.instance_name == self._old_instance_name:
911
        for idx, nic in enumerate(self.nics):
912
          if nic.mac == constants.VALUE_AUTO:
913
            nic_mac_ini = "nic%d_mac" % idx
914
            nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
915

    
916
    # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
917

    
918
    # ip ping checks (we use the same ip that was resolved in ExpandNames)
919
    if self.op.ip_check:
920
      if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
921
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
922
                                   (self.check_ip, self.op.instance_name),
923
                                   errors.ECODE_NOTUNIQUE)
924

    
925
    #### mac address generation
926
    # By generating here the mac address both the allocator and the hooks get
927
    # the real final mac address rather than the 'auto' or 'generate' value.
928
    # There is a race condition between the generation and the instance object
929
    # creation, which means that we know the mac is valid now, but we're not
930
    # sure it will be when we actually add the instance. If things go bad
931
    # adding the instance will abort because of a duplicate mac, and the
932
    # creation job will fail.
933
    for nic in self.nics:
934
      if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
935
        nic.mac = self.cfg.GenerateMAC(nic.network, self.proc.GetECId())
936

    
937
    #### allocator run
938

    
939
    if self.op.iallocator is not None:
940
      self._RunAllocator()
941

    
942
    # Release all unneeded node locks
943
    keep_locks = filter(None, [self.op.pnode, self.op.snode, self.op.src_node])
944
    ReleaseLocks(self, locking.LEVEL_NODE, keep=keep_locks)
945
    ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=keep_locks)
946
    ReleaseLocks(self, locking.LEVEL_NODE_ALLOC)
947

    
948
    assert (self.owned_locks(locking.LEVEL_NODE) ==
949
            self.owned_locks(locking.LEVEL_NODE_RES)), \
950
      "Node locks differ from node resource locks"
951

    
952
    #### node related checks
953

    
954
    # check primary node
955
    self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
956
    assert self.pnode is not None, \
957
      "Cannot retrieve locked node %s" % self.op.pnode
958
    if pnode.offline:
959
      raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
960
                                 pnode.name, errors.ECODE_STATE)
961
    if pnode.drained:
962
      raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
963
                                 pnode.name, errors.ECODE_STATE)
964
    if not pnode.vm_capable:
965
      raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
966
                                 " '%s'" % pnode.name, errors.ECODE_STATE)
967

    
968
    self.secondaries = []
969

    
970
    # Fill in any IPs from IP pools. This must happen here, because we need to
971
    # know the nic's primary node, as specified by the iallocator
972
    for idx, nic in enumerate(self.nics):
973
      net_uuid = nic.network
974
      if net_uuid is not None:
975
        nobj = self.cfg.GetNetwork(net_uuid)
976
        netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.name)
977
        if netparams is None:
978
          raise errors.OpPrereqError("No netparams found for network"
979
                                     " %s. Propably not connected to"
980
                                     " node's %s nodegroup" %
981
                                     (nobj.name, self.pnode.name),
982
                                     errors.ECODE_INVAL)
983
        self.LogInfo("NIC/%d inherits netparams %s" %
984
                     (idx, netparams.values()))
985
        nic.nicparams = dict(netparams)
986
        if nic.ip is not None:
987
          if nic.ip.lower() == constants.NIC_IP_POOL:
988
            try:
989
              nic.ip = self.cfg.GenerateIp(net_uuid, self.proc.GetECId())
990
            except errors.ReservationError:
991
              raise errors.OpPrereqError("Unable to get a free IP for NIC %d"
992
                                         " from the address pool" % idx,
993
                                         errors.ECODE_STATE)
994
            self.LogInfo("Chose IP %s from network %s", nic.ip, nobj.name)
995
          else:
996
            try:
997
              self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId())
998
            except errors.ReservationError:
999
              raise errors.OpPrereqError("IP address %s already in use"
1000
                                         " or does not belong to network %s" %
1001
                                         (nic.ip, nobj.name),
1002
                                         errors.ECODE_NOTUNIQUE)
1003

    
1004
      # net is None, ip None or given
1005
      elif self.op.conflicts_check:
1006
        _CheckForConflictingIp(self, nic.ip, self.pnode.name)
1007

    
1008
    # mirror node verification
1009
    if self.op.disk_template in constants.DTS_INT_MIRROR:
1010
      if self.op.snode == pnode.name:
1011
        raise errors.OpPrereqError("The secondary node cannot be the"
1012
                                   " primary node", errors.ECODE_INVAL)
1013
      CheckNodeOnline(self, self.op.snode)
1014
      CheckNodeNotDrained(self, self.op.snode)
1015
      CheckNodeVmCapable(self, self.op.snode)
1016
      self.secondaries.append(self.op.snode)
1017

    
1018
      snode = self.cfg.GetNodeInfo(self.op.snode)
1019
      if pnode.group != snode.group:
1020
        self.LogWarning("The primary and secondary nodes are in two"
1021
                        " different node groups; the disk parameters"
1022
                        " from the first disk's node group will be"
1023
                        " used")
1024

    
1025
    if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
1026
      nodes = [pnode]
1027
      if self.op.disk_template in constants.DTS_INT_MIRROR:
1028
        nodes.append(snode)
1029
      has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
1030
      if compat.any(map(has_es, nodes)):
1031
        raise errors.OpPrereqError("Disk template %s not supported with"
1032
                                   " exclusive storage" % self.op.disk_template,
1033
                                   errors.ECODE_STATE)
1034

    
1035
    nodenames = [pnode.name] + self.secondaries
1036

    
1037
    if not self.adopt_disks:
1038
      if self.op.disk_template == constants.DT_RBD:
1039
        # _CheckRADOSFreeSpace() is just a placeholder.
1040
        # Any function that checks prerequisites can be placed here.
1041
        # Check if there is enough space on the RADOS cluster.
1042
        CheckRADOSFreeSpace()
1043
      elif self.op.disk_template == constants.DT_EXT:
1044
        # FIXME: Function that checks prereqs if needed
1045
        pass
1046
      else:
1047
        # Check lv size requirements, if not adopting
1048
        req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
1049
        CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
1050

    
1051
    elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
1052
      all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
1053
                                disk[constants.IDISK_ADOPT])
1054
                     for disk in self.disks])
1055
      if len(all_lvs) != len(self.disks):
1056
        raise errors.OpPrereqError("Duplicate volume names given for adoption",
1057
                                   errors.ECODE_INVAL)
1058
      for lv_name in all_lvs:
1059
        try:
1060
          # FIXME: lv_name here is "vg/lv" need to ensure that other calls
1061
          # to ReserveLV uses the same syntax
1062
          self.cfg.ReserveLV(lv_name, self.proc.GetECId())
1063
        except errors.ReservationError:
1064
          raise errors.OpPrereqError("LV named %s used by another instance" %
1065
                                     lv_name, errors.ECODE_NOTUNIQUE)
1066

    
1067
      vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
1068
      vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
1069

    
1070
      node_lvs = self.rpc.call_lv_list([pnode.name],
1071
                                       vg_names.payload.keys())[pnode.name]
1072
      node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
1073
      node_lvs = node_lvs.payload
1074

    
1075
      delta = all_lvs.difference(node_lvs.keys())
1076
      if delta:
1077
        raise errors.OpPrereqError("Missing logical volume(s): %s" %
1078
                                   utils.CommaJoin(delta),
1079
                                   errors.ECODE_INVAL)
1080
      online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
1081
      if online_lvs:
1082
        raise errors.OpPrereqError("Online logical volumes found, cannot"
1083
                                   " adopt: %s" % utils.CommaJoin(online_lvs),
1084
                                   errors.ECODE_STATE)
1085
      # update the size of disk based on what is found
1086
      for dsk in self.disks:
1087
        dsk[constants.IDISK_SIZE] = \
1088
          int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
1089
                                        dsk[constants.IDISK_ADOPT])][0]))
1090

    
1091
    elif self.op.disk_template == constants.DT_BLOCK:
1092
      # Normalize and de-duplicate device paths
1093
      all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
1094
                       for disk in self.disks])
1095
      if len(all_disks) != len(self.disks):
1096
        raise errors.OpPrereqError("Duplicate disk names given for adoption",
1097
                                   errors.ECODE_INVAL)
1098
      baddisks = [d for d in all_disks
1099
                  if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
1100
      if baddisks:
1101
        raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
1102
                                   " cannot be adopted" %
1103
                                   (utils.CommaJoin(baddisks),
1104
                                    constants.ADOPTABLE_BLOCKDEV_ROOT),
1105
                                   errors.ECODE_INVAL)
1106

    
1107
      node_disks = self.rpc.call_bdev_sizes([pnode.name],
1108
                                            list(all_disks))[pnode.name]
1109
      node_disks.Raise("Cannot get block device information from node %s" %
1110
                       pnode.name)
1111
      node_disks = node_disks.payload
1112
      delta = all_disks.difference(node_disks.keys())
1113
      if delta:
1114
        raise errors.OpPrereqError("Missing block device(s): %s" %
1115
                                   utils.CommaJoin(delta),
1116
                                   errors.ECODE_INVAL)
1117
      for dsk in self.disks:
1118
        dsk[constants.IDISK_SIZE] = \
1119
          int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
1120

    
1121
    # Verify instance specs
1122
    spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
1123
    ispec = {
1124
      constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
1125
      constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
1126
      constants.ISPEC_DISK_COUNT: len(self.disks),
1127
      constants.ISPEC_DISK_SIZE: [disk[constants.IDISK_SIZE]
1128
                                  for disk in self.disks],
1129
      constants.ISPEC_NIC_COUNT: len(self.nics),
1130
      constants.ISPEC_SPINDLE_USE: spindle_use,
1131
      }
1132

    
1133
    group_info = self.cfg.GetNodeGroup(pnode.group)
1134
    ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1135
    res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec,
1136
                                               self.op.disk_template)
1137
    if not self.op.ignore_ipolicy and res:
1138
      msg = ("Instance allocation to group %s (%s) violates policy: %s" %
1139
             (pnode.group, group_info.name, utils.CommaJoin(res)))
1140
      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1141

    
1142
    CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
1143

    
1144
    CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
1145
    # check OS parameters (remotely)
1146
    CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
1147

    
1148
    CheckNicsBridgesExist(self, self.nics, self.pnode.name)
1149

    
1150
    #TODO: _CheckExtParams (remotely)
1151
    # Check parameters for extstorage
1152

    
1153
    # memory check on primary node
1154
    #TODO(dynmem): use MINMEM for checking
1155
    if self.op.start:
1156
      CheckNodeFreeMemory(self, self.pnode.name,
1157
                          "creating instance %s" % self.op.instance_name,
1158
                          self.be_full[constants.BE_MAXMEM],
1159
                          self.op.hypervisor)
1160

    
1161
    self.dry_run_result = list(nodenames)
1162

    
1163
  def Exec(self, feedback_fn):
1164
    """Create and add the instance to the cluster.
1165

1166
    """
1167
    instance = self.op.instance_name
1168
    pnode_name = self.pnode.name
1169

    
1170
    assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
1171
                self.owned_locks(locking.LEVEL_NODE)), \
1172
      "Node locks differ from node resource locks"
1173
    assert not self.glm.is_owned(locking.LEVEL_NODE_ALLOC)
1174

    
1175
    ht_kind = self.op.hypervisor
1176
    if ht_kind in constants.HTS_REQ_PORT:
1177
      network_port = self.cfg.AllocatePort()
1178
    else:
1179
      network_port = None
1180

    
1181
    # This is ugly but we got a chicken-egg problem here
1182
    # We can only take the group disk parameters, as the instance
1183
    # has no disks yet (we are generating them right here).
1184
    node = self.cfg.GetNodeInfo(pnode_name)
1185
    nodegroup = self.cfg.GetNodeGroup(node.group)
1186
    disks = GenerateDiskTemplate(self,
1187
                                 self.op.disk_template,
1188
                                 instance, pnode_name,
1189
                                 self.secondaries,
1190
                                 self.disks,
1191
                                 self.instance_file_storage_dir,
1192
                                 self.op.file_driver,
1193
                                 0,
1194
                                 feedback_fn,
1195
                                 self.cfg.GetGroupDiskParams(nodegroup))
1196

    
1197
    iobj = objects.Instance(name=instance, os=self.op.os_type,
1198
                            primary_node=pnode_name,
1199
                            nics=self.nics, disks=disks,
1200
                            disk_template=self.op.disk_template,
1201
                            disks_active=False,
1202
                            admin_state=constants.ADMINST_DOWN,
1203
                            network_port=network_port,
1204
                            beparams=self.op.beparams,
1205
                            hvparams=self.op.hvparams,
1206
                            hypervisor=self.op.hypervisor,
1207
                            osparams=self.op.osparams,
1208
                            )
1209

    
1210
    if self.op.tags:
1211
      for tag in self.op.tags:
1212
        iobj.AddTag(tag)
1213

    
1214
    if self.adopt_disks:
1215
      if self.op.disk_template == constants.DT_PLAIN:
1216
        # rename LVs to the newly-generated names; we need to construct
1217
        # 'fake' LV disks with the old data, plus the new unique_id
1218
        tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
1219
        rename_to = []
1220
        for t_dsk, a_dsk in zip(tmp_disks, self.disks):
1221
          rename_to.append(t_dsk.logical_id)
1222
          t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
1223
          self.cfg.SetDiskID(t_dsk, pnode_name)
1224
        result = self.rpc.call_blockdev_rename(pnode_name,
1225
                                               zip(tmp_disks, rename_to))
1226
        result.Raise("Failed to rename adoped LVs")
1227
    else:
1228
      feedback_fn("* creating instance disks...")
1229
      try:
1230
        CreateDisks(self, iobj)
1231
      except errors.OpExecError:
1232
        self.LogWarning("Device creation failed")
1233
        self.cfg.ReleaseDRBDMinors(instance)
1234
        raise
1235

    
1236
    feedback_fn("adding instance %s to cluster config" % instance)
1237

    
1238
    self.cfg.AddInstance(iobj, self.proc.GetECId())
1239

    
1240
    # Declare that we don't want to remove the instance lock anymore, as we've
1241
    # added the instance to the config
1242
    del self.remove_locks[locking.LEVEL_INSTANCE]
1243

    
1244
    if self.op.mode == constants.INSTANCE_IMPORT:
1245
      # Release unused nodes
1246
      ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
1247
    else:
1248
      # Release all nodes
1249
      ReleaseLocks(self, locking.LEVEL_NODE)
1250

    
1251
    disk_abort = False
1252
    if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
1253
      feedback_fn("* wiping instance disks...")
1254
      try:
1255
        WipeDisks(self, iobj)
1256
      except errors.OpExecError, err:
1257
        logging.exception("Wiping disks failed")
1258
        self.LogWarning("Wiping instance disks failed (%s)", err)
1259
        disk_abort = True
1260

    
1261
    if disk_abort:
1262
      # Something is already wrong with the disks, don't do anything else
1263
      pass
1264
    elif self.op.wait_for_sync:
1265
      disk_abort = not WaitForSync(self, iobj)
1266
    elif iobj.disk_template in constants.DTS_INT_MIRROR:
1267
      # make sure the disks are not degraded (still sync-ing is ok)
1268
      feedback_fn("* checking mirrors status")
1269
      disk_abort = not WaitForSync(self, iobj, oneshot=True)
1270
    else:
1271
      disk_abort = False
1272

    
1273
    if disk_abort:
1274
      RemoveDisks(self, iobj)
1275
      self.cfg.RemoveInstance(iobj.name)
1276
      # Make sure the instance lock gets removed
1277
      self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
1278
      raise errors.OpExecError("There are some degraded disks for"
1279
                               " this instance")
1280

    
1281
    # instance disks are now active
1282
    iobj.disks_active = True
1283

    
1284
    # Release all node resource locks
1285
    ReleaseLocks(self, locking.LEVEL_NODE_RES)
1286

    
1287
    if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
1288
      # we need to set the disks ID to the primary node, since the
1289
      # preceding code might or might have not done it, depending on
1290
      # disk template and other options
1291
      for disk in iobj.disks:
1292
        self.cfg.SetDiskID(disk, pnode_name)
1293
      if self.op.mode == constants.INSTANCE_CREATE:
1294
        if not self.op.no_install:
1295
          pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
1296
                        not self.op.wait_for_sync)
1297
          if pause_sync:
1298
            feedback_fn("* pausing disk sync to install instance OS")
1299
            result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1300
                                                              (iobj.disks,
1301
                                                               iobj), True)
1302
            for idx, success in enumerate(result.payload):
1303
              if not success:
1304
                logging.warn("pause-sync of instance %s for disk %d failed",
1305
                             instance, idx)
1306

    
1307
          feedback_fn("* running the instance OS create scripts...")
1308
          # FIXME: pass debug option from opcode to backend
1309
          os_add_result = \
1310
            self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
1311
                                          self.op.debug_level)
1312
          if pause_sync:
1313
            feedback_fn("* resuming disk sync")
1314
            result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
1315
                                                              (iobj.disks,
1316
                                                               iobj), False)
1317
            for idx, success in enumerate(result.payload):
1318
              if not success:
1319
                logging.warn("resume-sync of instance %s for disk %d failed",
1320
                             instance, idx)
1321

    
1322
          os_add_result.Raise("Could not add os for instance %s"
1323
                              " on node %s" % (instance, pnode_name))
1324

    
1325
      else:
1326
        if self.op.mode == constants.INSTANCE_IMPORT:
1327
          feedback_fn("* running the instance OS import scripts...")
1328

    
1329
          transfers = []
1330

    
1331
          for idx, image in enumerate(self.src_images):
1332
            if not image:
1333
              continue
1334

    
1335
            # FIXME: pass debug option from opcode to backend
1336
            dt = masterd.instance.DiskTransfer("disk/%s" % idx,
1337
                                               constants.IEIO_FILE, (image, ),
1338
                                               constants.IEIO_SCRIPT,
1339
                                               (iobj.disks[idx], idx),
1340
                                               None)
1341
            transfers.append(dt)
1342

    
1343
          import_result = \
1344
            masterd.instance.TransferInstanceData(self, feedback_fn,
1345
                                                  self.op.src_node, pnode_name,
1346
                                                  self.pnode.secondary_ip,
1347
                                                  iobj, transfers)
1348
          if not compat.all(import_result):
1349
            self.LogWarning("Some disks for instance %s on node %s were not"
1350
                            " imported successfully" % (instance, pnode_name))
1351

    
1352
          rename_from = self._old_instance_name
1353

    
1354
        elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
1355
          feedback_fn("* preparing remote import...")
1356
          # The source cluster will stop the instance before attempting to make
1357
          # a connection. In some cases stopping an instance can take a long
1358
          # time, hence the shutdown timeout is added to the connection
1359
          # timeout.
1360
          connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
1361
                             self.op.source_shutdown_timeout)
1362
          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
1363

    
1364
          assert iobj.primary_node == self.pnode.name
1365
          disk_results = \
1366
            masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
1367
                                          self.source_x509_ca,
1368
                                          self._cds, timeouts)
1369
          if not compat.all(disk_results):
1370
            # TODO: Should the instance still be started, even if some disks
1371
            # failed to import (valid for local imports, too)?
1372
            self.LogWarning("Some disks for instance %s on node %s were not"
1373
                            " imported successfully" % (instance, pnode_name))
1374

    
1375
          rename_from = self.source_instance_name
1376

    
1377
        else:
1378
          # also checked in the prereq part
1379
          raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
1380
                                       % self.op.mode)
1381

    
1382
        # Run rename script on newly imported instance
1383
        assert iobj.name == instance
1384
        feedback_fn("Running rename script for %s" % instance)
1385
        result = self.rpc.call_instance_run_rename(pnode_name, iobj,
1386
                                                   rename_from,
1387
                                                   self.op.debug_level)
1388
        if result.fail_msg:
1389
          self.LogWarning("Failed to run rename script for %s on node"
1390
                          " %s: %s" % (instance, pnode_name, result.fail_msg))
1391

    
1392
    assert not self.owned_locks(locking.LEVEL_NODE_RES)
1393

    
1394
    if self.op.start:
1395
      iobj.admin_state = constants.ADMINST_UP
1396
      self.cfg.Update(iobj, feedback_fn)
1397
      logging.info("Starting instance %s on node %s", instance, pnode_name)
1398
      feedback_fn("* starting instance...")
1399
      result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
1400
                                            False, self.op.reason)
1401
      result.Raise("Could not start instance")
1402

    
1403
    return list(iobj.all_nodes)
1404

    
1405

    
1406
class LUInstanceRename(LogicalUnit):
1407
  """Rename an instance.
1408

1409
  """
1410
  HPATH = "instance-rename"
1411
  HTYPE = constants.HTYPE_INSTANCE
1412

    
1413
  def CheckArguments(self):
1414
    """Check arguments.
1415

1416
    """
1417
    if self.op.ip_check and not self.op.name_check:
1418
      # TODO: make the ip check more flexible and not depend on the name check
1419
      raise errors.OpPrereqError("IP address check requires a name check",
1420
                                 errors.ECODE_INVAL)
1421

    
1422
  def BuildHooksEnv(self):
1423
    """Build hooks env.
1424

1425
    This runs on master, primary and secondary nodes of the instance.
1426

1427
    """
1428
    env = BuildInstanceHookEnvByObject(self, self.instance)
1429
    env["INSTANCE_NEW_NAME"] = self.op.new_name
1430
    return env
1431

    
1432
  def BuildHooksNodes(self):
1433
    """Build hooks nodes.
1434

1435
    """
1436
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
1437
    return (nl, nl)
1438

    
1439
  def CheckPrereq(self):
1440
    """Check prerequisites.
1441

1442
    This checks that the instance is in the cluster and is not running.
1443

1444
    """
1445
    self.op.instance_name = ExpandInstanceName(self.cfg,
1446
                                               self.op.instance_name)
1447
    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1448
    assert instance is not None
1449
    CheckNodeOnline(self, instance.primary_node)
1450
    CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
1451
                       msg="cannot rename")
1452
    self.instance = instance
1453

    
1454
    new_name = self.op.new_name
1455
    if self.op.name_check:
1456
      hostname = _CheckHostnameSane(self, new_name)
1457
      new_name = self.op.new_name = hostname.name
1458
      if (self.op.ip_check and
1459
          netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
1460
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
1461
                                   (hostname.ip, new_name),
1462
                                   errors.ECODE_NOTUNIQUE)
1463

    
1464
    instance_list = self.cfg.GetInstanceList()
1465
    if new_name in instance_list and new_name != instance.name:
1466
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
1467
                                 new_name, errors.ECODE_EXISTS)
1468

    
1469
  def Exec(self, feedback_fn):
1470
    """Rename the instance.
1471

1472
    """
1473
    inst = self.instance
1474
    old_name = inst.name
1475

    
1476
    rename_file_storage = False
1477
    if (inst.disk_template in constants.DTS_FILEBASED and
1478
        self.op.new_name != inst.name):
1479
      old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1480
      rename_file_storage = True
1481

    
1482
    self.cfg.RenameInstance(inst.name, self.op.new_name)
1483
    # Change the instance lock. This is definitely safe while we hold the BGL.
1484
    # Otherwise the new lock would have to be added in acquired mode.
1485
    assert self.REQ_BGL
1486
    assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER)
1487
    self.glm.remove(locking.LEVEL_INSTANCE, old_name)
1488
    self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
1489

    
1490
    # re-read the instance from the configuration after rename
1491
    inst = self.cfg.GetInstanceInfo(self.op.new_name)
1492

    
1493
    if rename_file_storage:
1494
      new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
1495
      result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
1496
                                                     old_file_storage_dir,
1497
                                                     new_file_storage_dir)
1498
      result.Raise("Could not rename on node %s directory '%s' to '%s'"
1499
                   " (but the instance has been renamed in Ganeti)" %
1500
                   (inst.primary_node, old_file_storage_dir,
1501
                    new_file_storage_dir))
1502

    
1503
    StartInstanceDisks(self, inst, None)
1504
    # update info on disks
1505
    info = GetInstanceInfoText(inst)
1506
    for (idx, disk) in enumerate(inst.disks):
1507
      for node in inst.all_nodes:
1508
        self.cfg.SetDiskID(disk, node)
1509
        result = self.rpc.call_blockdev_setinfo(node, disk, info)
1510
        if result.fail_msg:
1511
          self.LogWarning("Error setting info on node %s for disk %s: %s",
1512
                          node, idx, result.fail_msg)
1513
    try:
1514
      result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
1515
                                                 old_name, self.op.debug_level)
1516
      msg = result.fail_msg
1517
      if msg:
1518
        msg = ("Could not run OS rename script for instance %s on node %s"
1519
               " (but the instance has been renamed in Ganeti): %s" %
1520
               (inst.name, inst.primary_node, msg))
1521
        self.LogWarning(msg)
1522
    finally:
1523
      ShutdownInstanceDisks(self, inst)
1524

    
1525
    return inst.name
1526

    
1527

    
1528
class LUInstanceRemove(LogicalUnit):
1529
  """Remove an instance.
1530

1531
  """
1532
  HPATH = "instance-remove"
1533
  HTYPE = constants.HTYPE_INSTANCE
1534
  REQ_BGL = False
1535

    
1536
  def ExpandNames(self):
1537
    self._ExpandAndLockInstance()
1538
    self.needed_locks[locking.LEVEL_NODE] = []
1539
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1540
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
1541

    
1542
  def DeclareLocks(self, level):
1543
    if level == locking.LEVEL_NODE:
1544
      self._LockInstancesNodes()
1545
    elif level == locking.LEVEL_NODE_RES:
1546
      # Copy node locks
1547
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1548
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1549

    
1550
  def BuildHooksEnv(self):
1551
    """Build hooks env.
1552

1553
    This runs on master, primary and secondary nodes of the instance.
1554

1555
    """
1556
    env = BuildInstanceHookEnvByObject(self, self.instance)
1557
    env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
1558
    return env
1559

    
1560
  def BuildHooksNodes(self):
1561
    """Build hooks nodes.
1562

1563
    """
1564
    nl = [self.cfg.GetMasterNode()]
1565
    nl_post = list(self.instance.all_nodes) + nl
1566
    return (nl, nl_post)
1567

    
1568
  def CheckPrereq(self):
1569
    """Check prerequisites.
1570

1571
    This checks that the instance is in the cluster.
1572

1573
    """
1574
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1575
    assert self.instance is not None, \
1576
      "Cannot retrieve locked instance %s" % self.op.instance_name
1577

    
1578
  def Exec(self, feedback_fn):
1579
    """Remove the instance.
1580

1581
    """
1582
    instance = self.instance
1583
    logging.info("Shutting down instance %s on node %s",
1584
                 instance.name, instance.primary_node)
1585

    
1586
    result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
1587
                                             self.op.shutdown_timeout,
1588
                                             self.op.reason)
1589
    msg = result.fail_msg
1590
    if msg:
1591
      if self.op.ignore_failures:
1592
        feedback_fn("Warning: can't shutdown instance: %s" % msg)
1593
      else:
1594
        raise errors.OpExecError("Could not shutdown instance %s on"
1595
                                 " node %s: %s" %
1596
                                 (instance.name, instance.primary_node, msg))
1597

    
1598
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1599
            self.owned_locks(locking.LEVEL_NODE_RES))
1600
    assert not (set(instance.all_nodes) -
1601
                self.owned_locks(locking.LEVEL_NODE)), \
1602
      "Not owning correct locks"
1603

    
1604
    RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
1605

    
1606

    
1607
class LUInstanceMove(LogicalUnit):
1608
  """Move an instance by data-copying.
1609

1610
  """
1611
  HPATH = "instance-move"
1612
  HTYPE = constants.HTYPE_INSTANCE
1613
  REQ_BGL = False
1614

    
1615
  def ExpandNames(self):
1616
    self._ExpandAndLockInstance()
1617
    target_node = ExpandNodeName(self.cfg, self.op.target_node)
1618
    self.op.target_node = target_node
1619
    self.needed_locks[locking.LEVEL_NODE] = [target_node]
1620
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1621
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
1622

    
1623
  def DeclareLocks(self, level):
1624
    if level == locking.LEVEL_NODE:
1625
      self._LockInstancesNodes(primary_only=True)
1626
    elif level == locking.LEVEL_NODE_RES:
1627
      # Copy node locks
1628
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1629
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1630

    
1631
  def BuildHooksEnv(self):
1632
    """Build hooks env.
1633

1634
    This runs on master, primary and secondary nodes of the instance.
1635

1636
    """
1637
    env = {
1638
      "TARGET_NODE": self.op.target_node,
1639
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
1640
      }
1641
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
1642
    return env
1643

    
1644
  def BuildHooksNodes(self):
1645
    """Build hooks nodes.
1646

1647
    """
1648
    nl = [
1649
      self.cfg.GetMasterNode(),
1650
      self.instance.primary_node,
1651
      self.op.target_node,
1652
      ]
1653
    return (nl, nl)
1654

    
1655
  def CheckPrereq(self):
1656
    """Check prerequisites.
1657

1658
    This checks that the instance is in the cluster.
1659

1660
    """
1661
    self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1662
    assert self.instance is not None, \
1663
      "Cannot retrieve locked instance %s" % self.op.instance_name
1664

    
1665
    if instance.disk_template not in constants.DTS_COPYABLE:
1666
      raise errors.OpPrereqError("Disk template %s not suitable for copying" %
1667
                                 instance.disk_template, errors.ECODE_STATE)
1668

    
1669
    node = self.cfg.GetNodeInfo(self.op.target_node)
1670
    assert node is not None, \
1671
      "Cannot retrieve locked node %s" % self.op.target_node
1672

    
1673
    self.target_node = target_node = node.name
1674

    
1675
    if target_node == instance.primary_node:
1676
      raise errors.OpPrereqError("Instance %s is already on the node %s" %
1677
                                 (instance.name, target_node),
1678
                                 errors.ECODE_STATE)
1679

    
1680
    bep = self.cfg.GetClusterInfo().FillBE(instance)
1681

    
1682
    for idx, dsk in enumerate(instance.disks):
1683
      if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
1684
        raise errors.OpPrereqError("Instance disk %d has a complex layout,"
1685
                                   " cannot copy" % idx, errors.ECODE_STATE)
1686

    
1687
    CheckNodeOnline(self, target_node)
1688
    CheckNodeNotDrained(self, target_node)
1689
    CheckNodeVmCapable(self, target_node)
1690
    cluster = self.cfg.GetClusterInfo()
1691
    group_info = self.cfg.GetNodeGroup(node.group)
1692
    ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1693
    CheckTargetNodeIPolicy(self, ipolicy, instance, node, self.cfg,
1694
                           ignore=self.op.ignore_ipolicy)
1695

    
1696
    if instance.admin_state == constants.ADMINST_UP:
1697
      # check memory requirements on the secondary node
1698
      CheckNodeFreeMemory(self, target_node,
1699
                          "failing over instance %s" %
1700
                          instance.name, bep[constants.BE_MAXMEM],
1701
                          instance.hypervisor)
1702
    else:
1703
      self.LogInfo("Not checking memory on the secondary node as"
1704
                   " instance will not be started")
1705

    
1706
    # check bridge existance
1707
    CheckInstanceBridgesExist(self, instance, node=target_node)
1708

    
1709
  def Exec(self, feedback_fn):
1710
    """Move an instance.
1711

1712
    The move is done by shutting it down on its present node, copying
1713
    the data over (slow) and starting it on the new node.
1714

1715
    """
1716
    instance = self.instance
1717

    
1718
    source_node = instance.primary_node
1719
    target_node = self.target_node
1720

    
1721
    self.LogInfo("Shutting down instance %s on source node %s",
1722
                 instance.name, source_node)
1723

    
1724
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1725
            self.owned_locks(locking.LEVEL_NODE_RES))
1726

    
1727
    result = self.rpc.call_instance_shutdown(source_node, instance,
1728
                                             self.op.shutdown_timeout,
1729
                                             self.op.reason)
1730
    msg = result.fail_msg
1731
    if msg:
1732
      if self.op.ignore_consistency:
1733
        self.LogWarning("Could not shutdown instance %s on node %s."
1734
                        " Proceeding anyway. Please make sure node"
1735
                        " %s is down. Error details: %s",
1736
                        instance.name, source_node, source_node, msg)
1737
      else:
1738
        raise errors.OpExecError("Could not shutdown instance %s on"
1739
                                 " node %s: %s" %
1740
                                 (instance.name, source_node, msg))
1741

    
1742
    # create the target disks
1743
    try:
1744
      CreateDisks(self, instance, target_node=target_node)
1745
    except errors.OpExecError:
1746
      self.LogWarning("Device creation failed")
1747
      self.cfg.ReleaseDRBDMinors(instance.name)
1748
      raise
1749

    
1750
    cluster_name = self.cfg.GetClusterInfo().cluster_name
1751

    
1752
    errs = []
1753
    # activate, get path, copy the data over
1754
    for idx, disk in enumerate(instance.disks):
1755
      self.LogInfo("Copying data for disk %d", idx)
1756
      result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
1757
                                               instance.name, True, idx)
1758
      if result.fail_msg:
1759
        self.LogWarning("Can't assemble newly created disk %d: %s",
1760
                        idx, result.fail_msg)
1761
        errs.append(result.fail_msg)
1762
        break
1763
      dev_path = result.payload
1764
      result = self.rpc.call_blockdev_export(source_node, (disk, instance),
1765
                                             target_node, dev_path,
1766
                                             cluster_name)
1767
      if result.fail_msg:
1768
        self.LogWarning("Can't copy data over for disk %d: %s",
1769
                        idx, result.fail_msg)
1770
        errs.append(result.fail_msg)
1771
        break
1772

    
1773
    if errs:
1774
      self.LogWarning("Some disks failed to copy, aborting")
1775
      try:
1776
        RemoveDisks(self, instance, target_node=target_node)
1777
      finally:
1778
        self.cfg.ReleaseDRBDMinors(instance.name)
1779
        raise errors.OpExecError("Errors during disk copy: %s" %
1780
                                 (",".join(errs),))
1781

    
1782
    instance.primary_node = target_node
1783
    self.cfg.Update(instance, feedback_fn)
1784

    
1785
    self.LogInfo("Removing the disks on the original node")
1786
    RemoveDisks(self, instance, target_node=source_node)
1787

    
1788
    # Only start the instance if it's marked as up
1789
    if instance.admin_state == constants.ADMINST_UP:
1790
      self.LogInfo("Starting instance %s on node %s",
1791
                   instance.name, target_node)
1792

    
1793
      disks_ok, _ = AssembleInstanceDisks(self, instance,
1794
                                          ignore_secondaries=True)
1795
      if not disks_ok:
1796
        ShutdownInstanceDisks(self, instance)
1797
        raise errors.OpExecError("Can't activate the instance's disks")
1798

    
1799
      result = self.rpc.call_instance_start(target_node,
1800
                                            (instance, None, None), False,
1801
                                            self.op.reason)
1802
      msg = result.fail_msg
1803
      if msg:
1804
        ShutdownInstanceDisks(self, instance)
1805
        raise errors.OpExecError("Could not start instance %s on node %s: %s" %
1806
                                 (instance.name, target_node, msg))
1807

    
1808

    
1809
class LUInstanceMultiAlloc(NoHooksLU):
1810
  """Allocates multiple instances at the same time.
1811

1812
  """
1813
  REQ_BGL = False
1814

    
1815
  def CheckArguments(self):
1816
    """Check arguments.
1817

1818
    """
1819
    nodes = []
1820
    for inst in self.op.instances:
1821
      if inst.iallocator is not None:
1822
        raise errors.OpPrereqError("iallocator are not allowed to be set on"
1823
                                   " instance objects", errors.ECODE_INVAL)
1824
      nodes.append(bool(inst.pnode))
1825
      if inst.disk_template in constants.DTS_INT_MIRROR:
1826
        nodes.append(bool(inst.snode))
1827

    
1828
    has_nodes = compat.any(nodes)
1829
    if compat.all(nodes) ^ has_nodes:
1830
      raise errors.OpPrereqError("There are instance objects providing"
1831
                                 " pnode/snode while others do not",
1832
                                 errors.ECODE_INVAL)
1833

    
1834
    if not has_nodes and self.op.iallocator is None:
1835
      default_iallocator = self.cfg.GetDefaultIAllocator()
1836
      if default_iallocator:
1837
        self.op.iallocator = default_iallocator
1838
      else:
1839
        raise errors.OpPrereqError("No iallocator or nodes on the instances"
1840
                                   " given and no cluster-wide default"
1841
                                   " iallocator found; please specify either"
1842
                                   " an iallocator or nodes on the instances"
1843
                                   " or set a cluster-wide default iallocator",
1844
                                   errors.ECODE_INVAL)
1845

    
1846
    _CheckOpportunisticLocking(self.op)
1847

    
1848
    dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
1849
    if dups:
1850
      raise errors.OpPrereqError("There are duplicate instance names: %s" %
1851
                                 utils.CommaJoin(dups), errors.ECODE_INVAL)
1852

    
1853
  def ExpandNames(self):
1854
    """Calculate the locks.
1855

1856
    """
1857
    self.share_locks = ShareAll()
1858
    self.needed_locks = {
1859
      # iallocator will select nodes and even if no iallocator is used,
1860
      # collisions with LUInstanceCreate should be avoided
1861
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
1862
      }
1863

    
1864
    if self.op.iallocator:
1865
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
1866
      self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
1867

    
1868
      if self.op.opportunistic_locking:
1869
        self.opportunistic_locks[locking.LEVEL_NODE] = True
1870
        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
1871
    else:
1872
      nodeslist = []
1873
      for inst in self.op.instances:
1874
        inst.pnode = ExpandNodeName(self.cfg, inst.pnode)
1875
        nodeslist.append(inst.pnode)
1876
        if inst.snode is not None:
1877
          inst.snode = ExpandNodeName(self.cfg, inst.snode)
1878
          nodeslist.append(inst.snode)
1879

    
1880
      self.needed_locks[locking.LEVEL_NODE] = nodeslist
1881
      # Lock resources of instance's primary and secondary nodes (copy to
1882
      # prevent accidential modification)
1883
      self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
1884

    
1885
  def CheckPrereq(self):
1886
    """Check prerequisite.
1887

1888
    """
1889
    if self.op.iallocator:
1890
      cluster = self.cfg.GetClusterInfo()
1891
      default_vg = self.cfg.GetVGName()
1892
      ec_id = self.proc.GetECId()
1893

    
1894
      if self.op.opportunistic_locking:
1895
        # Only consider nodes for which a lock is held
1896
        node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
1897
      else:
1898
        node_whitelist = None
1899

    
1900
      insts = [_CreateInstanceAllocRequest(op, ComputeDisks(op, default_vg),
1901
                                           _ComputeNics(op, cluster, None,
1902
                                                        self.cfg, ec_id),
1903
                                           _ComputeFullBeParams(op, cluster),
1904
                                           node_whitelist)
1905
               for op in self.op.instances]
1906

    
1907
      req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
1908
      ial = iallocator.IAllocator(self.cfg, self.rpc, req)
1909

    
1910
      ial.Run(self.op.iallocator)
1911

    
1912
      if not ial.success:
1913
        raise errors.OpPrereqError("Can't compute nodes using"
1914
                                   " iallocator '%s': %s" %
1915
                                   (self.op.iallocator, ial.info),
1916
                                   errors.ECODE_NORES)
1917

    
1918
      self.ia_result = ial.result
1919

    
1920
    if self.op.dry_run:
1921
      self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
1922
        constants.JOB_IDS_KEY: [],
1923
        })
1924

    
1925
  def _ConstructPartialResult(self):
1926
    """Contructs the partial result.
1927

1928
    """
1929
    if self.op.iallocator:
1930
      (allocatable, failed_insts) = self.ia_result
1931
      allocatable_insts = map(compat.fst, allocatable)
1932
    else:
1933
      allocatable_insts = [op.instance_name for op in self.op.instances]
1934
      failed_insts = []
1935

    
1936
    return {
1937
      opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY: allocatable_insts,
1938
      opcodes.OpInstanceMultiAlloc.FAILED_KEY: failed_insts,
1939
      }
1940

    
1941
  def Exec(self, feedback_fn):
1942
    """Executes the opcode.
1943

1944
    """
1945
    jobs = []
1946
    if self.op.iallocator:
1947
      op2inst = dict((op.instance_name, op) for op in self.op.instances)
1948
      (allocatable, failed) = self.ia_result
1949

    
1950
      for (name, nodes) in allocatable:
1951
        op = op2inst.pop(name)
1952

    
1953
        if len(nodes) > 1:
1954
          (op.pnode, op.snode) = nodes
1955
        else:
1956
          (op.pnode,) = nodes
1957

    
1958
        jobs.append([op])
1959

    
1960
      missing = set(op2inst.keys()) - set(failed)
1961
      assert not missing, \
1962
        "Iallocator did return incomplete result: %s" % \
1963
        utils.CommaJoin(missing)
1964
    else:
1965
      jobs.extend([op] for op in self.op.instances)
1966

    
1967
    return ResultWithJobs(jobs, **self._ConstructPartialResult())
1968

    
1969

    
1970
class _InstNicModPrivate:
1971
  """Data structure for network interface modifications.
1972

1973
  Used by L{LUInstanceSetParams}.
1974

1975
  """
1976
  def __init__(self):
1977
    self.params = None
1978
    self.filled = None
1979

    
1980

    
1981
def _PrepareContainerMods(mods, private_fn):
1982
  """Prepares a list of container modifications by adding a private data field.
1983

1984
  @type mods: list of tuples; (operation, index, parameters)
1985
  @param mods: List of modifications
1986
  @type private_fn: callable or None
1987
  @param private_fn: Callable for constructing a private data field for a
1988
    modification
1989
  @rtype: list
1990

1991
  """
1992
  if private_fn is None:
1993
    fn = lambda: None
1994
  else:
1995
    fn = private_fn
1996

    
1997
  return [(op, idx, params, fn()) for (op, idx, params) in mods]
1998

    
1999

    
2000
def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
2001
  """Checks if nodes have enough physical CPUs
2002

2003
  This function checks if all given nodes have the needed number of
2004
  physical CPUs. In case any node has less CPUs or we cannot get the
2005
  information from the node, this function raises an OpPrereqError
2006
  exception.
2007

2008
  @type lu: C{LogicalUnit}
2009
  @param lu: a logical unit from which we get configuration data
2010
  @type nodenames: C{list}
2011
  @param nodenames: the list of node names to check
2012
  @type requested: C{int}
2013
  @param requested: the minimum acceptable number of physical CPUs
2014
  @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
2015
      or we cannot check the node
2016

2017
  """
2018
  nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name], None)
2019
  for node in nodenames:
2020
    info = nodeinfo[node]
2021
    info.Raise("Cannot get current information from node %s" % node,
2022
               prereq=True, ecode=errors.ECODE_ENVIRON)
2023
    (_, _, (hv_info, )) = info.payload
2024
    num_cpus = hv_info.get("cpu_total", None)
2025
    if not isinstance(num_cpus, int):
2026
      raise errors.OpPrereqError("Can't compute the number of physical CPUs"
2027
                                 " on node %s, result was '%s'" %
2028
                                 (node, num_cpus), errors.ECODE_ENVIRON)
2029
    if requested > num_cpus:
2030
      raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
2031
                                 "required" % (node, num_cpus, requested),
2032
                                 errors.ECODE_NORES)
2033

    
2034

    
2035
def GetItemFromContainer(identifier, kind, container):
2036
  """Return the item refered by the identifier.
2037

2038
  @type identifier: string
2039
  @param identifier: Item index or name or UUID
2040
  @type kind: string
2041
  @param kind: One-word item description
2042
  @type container: list
2043
  @param container: Container to get the item from
2044

2045
  """
2046
  # Index
2047
  try:
2048
    idx = int(identifier)
2049
    if idx == -1:
2050
      # Append
2051
      absidx = len(container) - 1
2052
    elif idx < 0:
2053
      raise IndexError("Not accepting negative indices other than -1")
2054
    elif idx > len(container):
2055
      raise IndexError("Got %s index %s, but there are only %s" %
2056
                       (kind, idx, len(container)))
2057
    else:
2058
      absidx = idx
2059
    return (absidx, container[idx])
2060
  except ValueError:
2061
    pass
2062

    
2063
  for idx, item in enumerate(container):
2064
    if item.uuid == identifier or item.name == identifier:
2065
      return (idx, item)
2066

    
2067
  raise errors.OpPrereqError("Cannot find %s with identifier %s" %
2068
                             (kind, identifier), errors.ECODE_NOENT)
2069

    
2070

    
2071
def _ApplyContainerMods(kind, container, chgdesc, mods,
2072
                        create_fn, modify_fn, remove_fn):
2073
  """Applies descriptions in C{mods} to C{container}.
2074

2075
  @type kind: string
2076
  @param kind: One-word item description
2077
  @type container: list
2078
  @param container: Container to modify
2079
  @type chgdesc: None or list
2080
  @param chgdesc: List of applied changes
2081
  @type mods: list
2082
  @param mods: Modifications as returned by L{_PrepareContainerMods}
2083
  @type create_fn: callable
2084
  @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
2085
    receives absolute item index, parameters and private data object as added
2086
    by L{_PrepareContainerMods}, returns tuple containing new item and changes
2087
    as list
2088
  @type modify_fn: callable
2089
  @param modify_fn: Callback for modifying an existing item
2090
    (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
2091
    and private data object as added by L{_PrepareContainerMods}, returns
2092
    changes as list
2093
  @type remove_fn: callable
2094
  @param remove_fn: Callback on removing item; receives absolute item index,
2095
    item and private data object as added by L{_PrepareContainerMods}
2096

2097
  """
2098
  for (op, identifier, params, private) in mods:
2099
    changes = None
2100

    
2101
    if op == constants.DDM_ADD:
2102
      # Calculate where item will be added
2103
      # When adding an item, identifier can only be an index
2104
      try:
2105
        idx = int(identifier)
2106
      except ValueError:
2107
        raise errors.OpPrereqError("Only possitive integer or -1 is accepted as"
2108
                                   " identifier for %s" % constants.DDM_ADD,
2109
                                   errors.ECODE_INVAL)
2110
      if idx == -1:
2111
        addidx = len(container)
2112
      else:
2113
        if idx < 0:
2114
          raise IndexError("Not accepting negative indices other than -1")
2115
        elif idx > len(container):
2116
          raise IndexError("Got %s index %s, but there are only %s" %
2117
                           (kind, idx, len(container)))
2118
        addidx = idx
2119

    
2120
      if create_fn is None:
2121
        item = params
2122
      else:
2123
        (item, changes) = create_fn(addidx, params, private)
2124

    
2125
      if idx == -1:
2126
        container.append(item)
2127
      else:
2128
        assert idx >= 0
2129
        assert idx <= len(container)
2130
        # list.insert does so before the specified index
2131
        container.insert(idx, item)
2132
    else:
2133
      # Retrieve existing item
2134
      (absidx, item) = GetItemFromContainer(identifier, kind, container)
2135

    
2136
      if op == constants.DDM_REMOVE:
2137
        assert not params
2138

    
2139
        if remove_fn is not None:
2140
          remove_fn(absidx, item, private)
2141

    
2142
        changes = [("%s/%s" % (kind, absidx), "remove")]
2143

    
2144
        assert container[absidx] == item
2145
        del container[absidx]
2146
      elif op == constants.DDM_MODIFY:
2147
        if modify_fn is not None:
2148
          changes = modify_fn(absidx, item, params, private)
2149
      else:
2150
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2151

    
2152
    assert _TApplyContModsCbChanges(changes)
2153

    
2154
    if not (chgdesc is None or changes is None):
2155
      chgdesc.extend(changes)
2156

    
2157

    
2158
def _UpdateIvNames(base_index, disks):
2159
  """Updates the C{iv_name} attribute of disks.
2160

2161
  @type disks: list of L{objects.Disk}
2162

2163
  """
2164
  for (idx, disk) in enumerate(disks):
2165
    disk.iv_name = "disk/%s" % (base_index + idx, )
2166

    
2167

    
2168
class LUInstanceSetParams(LogicalUnit):
2169
  """Modifies an instances's parameters.
2170

2171
  """
2172
  HPATH = "instance-modify"
2173
  HTYPE = constants.HTYPE_INSTANCE
2174
  REQ_BGL = False
2175

    
2176
  @staticmethod
2177
  def _UpgradeDiskNicMods(kind, mods, verify_fn):
2178
    assert ht.TList(mods)
2179
    assert not mods or len(mods[0]) in (2, 3)
2180

    
2181
    if mods and len(mods[0]) == 2:
2182
      result = []
2183

    
2184
      addremove = 0
2185
      for op, params in mods:
2186
        if op in (constants.DDM_ADD, constants.DDM_REMOVE):
2187
          result.append((op, -1, params))
2188
          addremove += 1
2189

    
2190
          if addremove > 1:
2191
            raise errors.OpPrereqError("Only one %s add or remove operation is"
2192
                                       " supported at a time" % kind,
2193
                                       errors.ECODE_INVAL)
2194
        else:
2195
          result.append((constants.DDM_MODIFY, op, params))
2196

    
2197
      assert verify_fn(result)
2198
    else:
2199
      result = mods
2200

    
2201
    return result
2202

    
2203
  @staticmethod
2204
  def _CheckMods(kind, mods, key_types, item_fn):
2205
    """Ensures requested disk/NIC modifications are valid.
2206

2207
    """
2208
    for (op, _, params) in mods:
2209
      assert ht.TDict(params)
2210

    
2211
      # If 'key_types' is an empty dict, we assume we have an
2212
      # 'ext' template and thus do not ForceDictType
2213
      if key_types:
2214
        utils.ForceDictType(params, key_types)
2215

    
2216
      if op == constants.DDM_REMOVE:
2217
        if params:
2218
          raise errors.OpPrereqError("No settings should be passed when"
2219
                                     " removing a %s" % kind,
2220
                                     errors.ECODE_INVAL)
2221
      elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
2222
        item_fn(op, params)
2223
      else:
2224
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2225

    
2226
  @staticmethod
2227
  def _VerifyDiskModification(op, params):
2228
    """Verifies a disk modification.
2229

2230
    """
2231
    if op == constants.DDM_ADD:
2232
      mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
2233
      if mode not in constants.DISK_ACCESS_SET:
2234
        raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
2235
                                   errors.ECODE_INVAL)
2236

    
2237
      size = params.get(constants.IDISK_SIZE, None)
2238
      if size is None:
2239
        raise errors.OpPrereqError("Required disk parameter '%s' missing" %
2240
                                   constants.IDISK_SIZE, errors.ECODE_INVAL)
2241

    
2242
      try:
2243
        size = int(size)
2244
      except (TypeError, ValueError), err:
2245
        raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
2246
                                   errors.ECODE_INVAL)
2247

    
2248
      params[constants.IDISK_SIZE] = size
2249
      name = params.get(constants.IDISK_NAME, None)
2250
      if name is not None and name.lower() == constants.VALUE_NONE:
2251
        params[constants.IDISK_NAME] = None
2252

    
2253
    elif op == constants.DDM_MODIFY:
2254
      if constants.IDISK_SIZE in params:
2255
        raise errors.OpPrereqError("Disk size change not possible, use"
2256
                                   " grow-disk", errors.ECODE_INVAL)
2257
      if len(params) > 2:
2258
        raise errors.OpPrereqError("Disk modification doesn't support"
2259
                                   " additional arbitrary parameters",
2260
                                   errors.ECODE_INVAL)
2261
      name = params.get(constants.IDISK_NAME, None)
2262
      if name is not None and name.lower() == constants.VALUE_NONE:
2263
        params[constants.IDISK_NAME] = None
2264

    
2265
  @staticmethod
2266
  def _VerifyNicModification(op, params):
2267
    """Verifies a network interface modification.
2268

2269
    """
2270
    if op in (constants.DDM_ADD, constants.DDM_MODIFY):
2271
      ip = params.get(constants.INIC_IP, None)
2272
      name = params.get(constants.INIC_NAME, None)
2273
      req_net = params.get(constants.INIC_NETWORK, None)
2274
      link = params.get(constants.NIC_LINK, None)
2275
      mode = params.get(constants.NIC_MODE, None)
2276
      if name is not None and name.lower() == constants.VALUE_NONE:
2277
        params[constants.INIC_NAME] = None
2278
      if req_net is not None:
2279
        if req_net.lower() == constants.VALUE_NONE:
2280
          params[constants.INIC_NETWORK] = None
2281
          req_net = None
2282
        elif link is not None or mode is not None:
2283
          raise errors.OpPrereqError("If network is given"
2284
                                     " mode or link should not",
2285
                                     errors.ECODE_INVAL)
2286

    
2287
      if op == constants.DDM_ADD:
2288
        macaddr = params.get(constants.INIC_MAC, None)
2289
        if macaddr is None:
2290
          params[constants.INIC_MAC] = constants.VALUE_AUTO
2291

    
2292
      if ip is not None:
2293
        if ip.lower() == constants.VALUE_NONE:
2294
          params[constants.INIC_IP] = None
2295
        else:
2296
          if ip.lower() == constants.NIC_IP_POOL:
2297
            if op == constants.DDM_ADD and req_net is None:
2298
              raise errors.OpPrereqError("If ip=pool, parameter network"
2299
                                         " cannot be none",
2300
                                         errors.ECODE_INVAL)
2301
          else:
2302
            if not netutils.IPAddress.IsValid(ip):
2303
              raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
2304
                                         errors.ECODE_INVAL)
2305

    
2306
      if constants.INIC_MAC in params:
2307
        macaddr = params[constants.INIC_MAC]
2308
        if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2309
          macaddr = utils.NormalizeAndValidateMac(macaddr)
2310

    
2311
        if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
2312
          raise errors.OpPrereqError("'auto' is not a valid MAC address when"
2313
                                     " modifying an existing NIC",
2314
                                     errors.ECODE_INVAL)
2315

    
2316
  def CheckArguments(self):
2317
    if not (self.op.nics or self.op.disks or self.op.disk_template or
2318
            self.op.hvparams or self.op.beparams or self.op.os_name or
2319
            self.op.osparams or self.op.offline is not None or
2320
            self.op.runtime_mem or self.op.pnode):
2321
      raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
2322

    
2323
    if self.op.hvparams:
2324
      CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
2325
                           "hypervisor", "instance", "cluster")
2326

    
2327
    self.op.disks = self._UpgradeDiskNicMods(
2328
      "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
2329
    self.op.nics = self._UpgradeDiskNicMods(
2330
      "NIC", self.op.nics, opcodes.OpInstanceSetParams.TestNicModifications)
2331

    
2332
    if self.op.disks and self.op.disk_template is not None:
2333
      raise errors.OpPrereqError("Disk template conversion and other disk"
2334
                                 " changes not supported at the same time",
2335
                                 errors.ECODE_INVAL)
2336

    
2337
    if (self.op.disk_template and
2338
        self.op.disk_template in constants.DTS_INT_MIRROR and
2339
        self.op.remote_node is None):
2340
      raise errors.OpPrereqError("Changing the disk template to a mirrored"
2341
                                 " one requires specifying a secondary node",
2342
                                 errors.ECODE_INVAL)
2343

    
2344
    # Check NIC modifications
2345
    self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
2346
                    self._VerifyNicModification)
2347

    
2348
    if self.op.pnode:
2349
      self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
2350

    
2351
  def ExpandNames(self):
2352
    self._ExpandAndLockInstance()
2353
    self.needed_locks[locking.LEVEL_NODEGROUP] = []
2354
    # Can't even acquire node locks in shared mode as upcoming changes in
2355
    # Ganeti 2.6 will start to modify the node object on disk conversion
2356
    self.needed_locks[locking.LEVEL_NODE] = []
2357
    self.needed_locks[locking.LEVEL_NODE_RES] = []
2358
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2359
    # Look node group to look up the ipolicy
2360
    self.share_locks[locking.LEVEL_NODEGROUP] = 1
2361

    
2362
  def DeclareLocks(self, level):
2363
    if level == locking.LEVEL_NODEGROUP:
2364
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
2365
      # Acquire locks for the instance's nodegroups optimistically. Needs
2366
      # to be verified in CheckPrereq
2367
      self.needed_locks[locking.LEVEL_NODEGROUP] = \
2368
        self.cfg.GetInstanceNodeGroups(self.op.instance_name)
2369
    elif level == locking.LEVEL_NODE:
2370
      self._LockInstancesNodes()
2371
      if self.op.disk_template and self.op.remote_node:
2372
        self.op.remote_node = ExpandNodeName(self.cfg, self.op.remote_node)
2373
        self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
2374
    elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
2375
      # Copy node locks
2376
      self.needed_locks[locking.LEVEL_NODE_RES] = \
2377
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
2378

    
2379
  def BuildHooksEnv(self):
2380
    """Build hooks env.
2381

2382
    This runs on the master, primary and secondaries.
2383

2384
    """
2385
    args = {}
2386
    if constants.BE_MINMEM in self.be_new:
2387
      args["minmem"] = self.be_new[constants.BE_MINMEM]
2388
    if constants.BE_MAXMEM in self.be_new:
2389
      args["maxmem"] = self.be_new[constants.BE_MAXMEM]
2390
    if constants.BE_VCPUS in self.be_new:
2391
      args["vcpus"] = self.be_new[constants.BE_VCPUS]
2392
    # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
2393
    # information at all.
2394

    
2395
    if self._new_nics is not None:
2396
      nics = []
2397

    
2398
      for nic in self._new_nics:
2399
        n = copy.deepcopy(nic)
2400
        nicparams = self.cluster.SimpleFillNIC(n.nicparams)
2401
        n.nicparams = nicparams
2402
        nics.append(NICToTuple(self, n))
2403

    
2404
      args["nics"] = nics
2405

    
2406
    env = BuildInstanceHookEnvByObject(self, self.instance, override=args)
2407
    if self.op.disk_template:
2408
      env["NEW_DISK_TEMPLATE"] = self.op.disk_template
2409
    if self.op.runtime_mem:
2410
      env["RUNTIME_MEMORY"] = self.op.runtime_mem
2411

    
2412
    return env
2413

    
2414
  def BuildHooksNodes(self):
2415
    """Build hooks nodes.
2416

2417
    """
2418
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
2419
    return (nl, nl)
2420

    
2421
  def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
2422
                              old_params, cluster, pnode):
2423

    
2424
    update_params_dict = dict([(key, params[key])
2425
                               for key in constants.NICS_PARAMETERS
2426
                               if key in params])
2427

    
2428
    req_link = update_params_dict.get(constants.NIC_LINK, None)
2429
    req_mode = update_params_dict.get(constants.NIC_MODE, None)
2430

    
2431
    new_net_uuid = None
2432
    new_net_uuid_or_name = params.get(constants.INIC_NETWORK, old_net_uuid)
2433
    if new_net_uuid_or_name:
2434
      new_net_uuid = self.cfg.LookupNetwork(new_net_uuid_or_name)
2435
      new_net_obj = self.cfg.GetNetwork(new_net_uuid)
2436

    
2437
    if old_net_uuid:
2438
      old_net_obj = self.cfg.GetNetwork(old_net_uuid)
2439

    
2440
    if new_net_uuid:
2441
      netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode)
2442
      if not netparams:
2443
        raise errors.OpPrereqError("No netparams found for the network"
2444
                                   " %s, probably not connected" %
2445
                                   new_net_obj.name, errors.ECODE_INVAL)
2446
      new_params = dict(netparams)
2447
    else:
2448
      new_params = GetUpdatedParams(old_params, update_params_dict)
2449

    
2450
    utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
2451

    
2452
    new_filled_params = cluster.SimpleFillNIC(new_params)
2453
    objects.NIC.CheckParameterSyntax(new_filled_params)
2454

    
2455
    new_mode = new_filled_params[constants.NIC_MODE]
2456
    if new_mode == constants.NIC_MODE_BRIDGED:
2457
      bridge = new_filled_params[constants.NIC_LINK]
2458
      msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
2459
      if msg:
2460
        msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
2461
        if self.op.force:
2462
          self.warn.append(msg)
2463
        else:
2464
          raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
2465

    
2466
    elif new_mode == constants.NIC_MODE_ROUTED:
2467
      ip = params.get(constants.INIC_IP, old_ip)
2468
      if ip is None:
2469
        raise errors.OpPrereqError("Cannot set the NIC IP address to None"
2470
                                   " on a routed NIC", errors.ECODE_INVAL)
2471

    
2472
    elif new_mode == constants.NIC_MODE_OVS:
2473
      # TODO: check OVS link
2474
      self.LogInfo("OVS links are currently not checked for correctness")
2475

    
2476
    if constants.INIC_MAC in params:
2477
      mac = params[constants.INIC_MAC]
2478
      if mac is None:
2479
        raise errors.OpPrereqError("Cannot unset the NIC MAC address",
2480
                                   errors.ECODE_INVAL)
2481
      elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2482
        # otherwise generate the MAC address
2483
        params[constants.INIC_MAC] = \
2484
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2485
      else:
2486
        # or validate/reserve the current one
2487
        try:
2488
          self.cfg.ReserveMAC(mac, self.proc.GetECId())
2489
        except errors.ReservationError:
2490
          raise errors.OpPrereqError("MAC address '%s' already in use"
2491
                                     " in cluster" % mac,
2492
                                     errors.ECODE_NOTUNIQUE)
2493
    elif new_net_uuid != old_net_uuid:
2494

    
2495
      def get_net_prefix(net_uuid):
2496
        mac_prefix = None
2497
        if net_uuid:
2498
          nobj = self.cfg.GetNetwork(net_uuid)
2499
          mac_prefix = nobj.mac_prefix
2500

    
2501
        return mac_prefix
2502

    
2503
      new_prefix = get_net_prefix(new_net_uuid)
2504
      old_prefix = get_net_prefix(old_net_uuid)
2505
      if old_prefix != new_prefix:
2506
        params[constants.INIC_MAC] = \
2507
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2508

    
2509
    # if there is a change in (ip, network) tuple
2510
    new_ip = params.get(constants.INIC_IP, old_ip)
2511
    if (new_ip, new_net_uuid) != (old_ip, old_net_uuid):
2512
      if new_ip:
2513
        # if IP is pool then require a network and generate one IP
2514
        if new_ip.lower() == constants.NIC_IP_POOL:
2515
          if new_net_uuid:
2516
            try:
2517
              new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId())
2518
            except errors.ReservationError:
2519
              raise errors.OpPrereqError("Unable to get a free IP"
2520
                                         " from the address pool",
2521
                                         errors.ECODE_STATE)
2522
            self.LogInfo("Chose IP %s from network %s",
2523
                         new_ip,
2524
                         new_net_obj.name)
2525
            params[constants.INIC_IP] = new_ip
2526
          else:
2527
            raise errors.OpPrereqError("ip=pool, but no network found",
2528
                                       errors.ECODE_INVAL)
2529
        # Reserve new IP if in the new network if any
2530
        elif new_net_uuid:
2531
          try:
2532
            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId())
2533
            self.LogInfo("Reserving IP %s in network %s",
2534
                         new_ip, new_net_obj.name)
2535
          except errors.ReservationError:
2536
            raise errors.OpPrereqError("IP %s not available in network %s" %
2537
                                       (new_ip, new_net_obj.name),
2538
                                       errors.ECODE_NOTUNIQUE)
2539
        # new network is None so check if new IP is a conflicting IP
2540
        elif self.op.conflicts_check:
2541
          _CheckForConflictingIp(self, new_ip, pnode)
2542

    
2543
      # release old IP if old network is not None
2544
      if old_ip and old_net_uuid:
2545
        try:
2546
          self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId())
2547
        except errors.AddressPoolError:
2548
          logging.warning("Release IP %s not contained in network %s",
2549
                          old_ip, old_net_obj.name)
2550

    
2551
    # there are no changes in (ip, network) tuple and old network is not None
2552
    elif (old_net_uuid is not None and
2553
          (req_link is not None or req_mode is not None)):
2554
      raise errors.OpPrereqError("Not allowed to change link or mode of"
2555
                                 " a NIC that is connected to a network",
2556
                                 errors.ECODE_INVAL)
2557

    
2558
    private.params = new_params
2559
    private.filled = new_filled_params
2560

    
2561
  def _PreCheckDiskTemplate(self, pnode_info):
2562
    """CheckPrereq checks related to a new disk template."""
2563
    # Arguments are passed to avoid configuration lookups
2564
    instance = self.instance
2565
    pnode = instance.primary_node
2566
    cluster = self.cluster
2567
    if instance.disk_template == self.op.disk_template:
2568
      raise errors.OpPrereqError("Instance already has disk template %s" %
2569
                                 instance.disk_template, errors.ECODE_INVAL)
2570

    
2571
    if (instance.disk_template,
2572
        self.op.disk_template) not in self._DISK_CONVERSIONS:
2573
      raise errors.OpPrereqError("Unsupported disk template conversion from"
2574
                                 " %s to %s" % (instance.disk_template,
2575
                                                self.op.disk_template),
2576
                                 errors.ECODE_INVAL)
2577
    CheckInstanceState(self, instance, INSTANCE_DOWN,
2578
                       msg="cannot change disk template")
2579
    if self.op.disk_template in constants.DTS_INT_MIRROR:
2580
      if self.op.remote_node == pnode:
2581
        raise errors.OpPrereqError("Given new secondary node %s is the same"
2582
                                   " as the primary node of the instance" %
2583
                                   self.op.remote_node, errors.ECODE_STATE)
2584
      CheckNodeOnline(self, self.op.remote_node)
2585
      CheckNodeNotDrained(self, self.op.remote_node)
2586
      # FIXME: here we assume that the old instance type is DT_PLAIN
2587
      assert instance.disk_template == constants.DT_PLAIN
2588
      disks = [{constants.IDISK_SIZE: d.size,
2589
                constants.IDISK_VG: d.logical_id[0]}
2590
               for d in instance.disks]
2591
      required = ComputeDiskSizePerVG(self.op.disk_template, disks)
2592
      CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
2593

    
2594
      snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
2595
      snode_group = self.cfg.GetNodeGroup(snode_info.group)
2596
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2597
                                                              snode_group)
2598
      CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info, self.cfg,
2599
                             ignore=self.op.ignore_ipolicy)
2600
      if pnode_info.group != snode_info.group:
2601
        self.LogWarning("The primary and secondary nodes are in two"
2602
                        " different node groups; the disk parameters"
2603
                        " from the first disk's node group will be"
2604
                        " used")
2605

    
2606
    if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
2607
      # Make sure none of the nodes require exclusive storage
2608
      nodes = [pnode_info]
2609
      if self.op.disk_template in constants.DTS_INT_MIRROR:
2610
        assert snode_info
2611
        nodes.append(snode_info)
2612
      has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
2613
      if compat.any(map(has_es, nodes)):
2614
        errmsg = ("Cannot convert disk template from %s to %s when exclusive"
2615
                  " storage is enabled" % (instance.disk_template,
2616
                                           self.op.disk_template))
2617
        raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
2618

    
2619
  def CheckPrereq(self):
2620
    """Check prerequisites.
2621

2622
    This only checks the instance list against the existing names.
2623

2624
    """
2625
    assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
2626
    instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
2627

    
2628
    cluster = self.cluster = self.cfg.GetClusterInfo()
2629
    assert self.instance is not None, \
2630
      "Cannot retrieve locked instance %s" % self.op.instance_name
2631

    
2632
    pnode = instance.primary_node
2633

    
2634
    self.warn = []
2635

    
2636
    if (self.op.pnode is not None and self.op.pnode != pnode and
2637
        not self.op.force):
2638
      # verify that the instance is not up
2639
      instance_info = self.rpc.call_instance_info(pnode, instance.name,
2640
                                                  instance.hypervisor)
2641
      if instance_info.fail_msg:
2642
        self.warn.append("Can't get instance runtime information: %s" %
2643
                         instance_info.fail_msg)
2644
      elif instance_info.payload:
2645
        raise errors.OpPrereqError("Instance is still running on %s" % pnode,
2646
                                   errors.ECODE_STATE)
2647

    
2648
    assert pnode in self.owned_locks(locking.LEVEL_NODE)
2649
    nodelist = list(instance.all_nodes)
2650
    pnode_info = self.cfg.GetNodeInfo(pnode)
2651
    self.diskparams = self.cfg.GetInstanceDiskParams(instance)
2652

    
2653
    #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
2654
    assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
2655
    group_info = self.cfg.GetNodeGroup(pnode_info.group)
2656

    
2657
    # dictionary with instance information after the modification
2658
    ispec = {}
2659

    
2660
    # Check disk modifications. This is done here and not in CheckArguments
2661
    # (as with NICs), because we need to know the instance's disk template
2662
    if instance.disk_template == constants.DT_EXT:
2663
      self._CheckMods("disk", self.op.disks, {},
2664
                      self._VerifyDiskModification)
2665
    else:
2666
      self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
2667
                      self._VerifyDiskModification)
2668

    
2669
    # Prepare disk/NIC modifications
2670
    self.diskmod = _PrepareContainerMods(self.op.disks, None)
2671
    self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
2672

    
2673
    # Check the validity of the `provider' parameter
2674
    if instance.disk_template in constants.DT_EXT:
2675
      for mod in self.diskmod:
2676
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2677
        if mod[0] == constants.DDM_ADD:
2678
          if ext_provider is None:
2679
            raise errors.OpPrereqError("Instance template is '%s' and parameter"
2680
                                       " '%s' missing, during disk add" %
2681
                                       (constants.DT_EXT,
2682
                                        constants.IDISK_PROVIDER),
2683
                                       errors.ECODE_NOENT)
2684
        elif mod[0] == constants.DDM_MODIFY:
2685
          if ext_provider:
2686
            raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
2687
                                       " modification" %
2688
                                       constants.IDISK_PROVIDER,
2689
                                       errors.ECODE_INVAL)
2690
    else:
2691
      for mod in self.diskmod:
2692
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2693
        if ext_provider is not None:
2694
          raise errors.OpPrereqError("Parameter '%s' is only valid for"
2695
                                     " instances of type '%s'" %
2696
                                     (constants.IDISK_PROVIDER,
2697
                                      constants.DT_EXT),
2698
                                     errors.ECODE_INVAL)
2699

    
2700
    # OS change
2701
    if self.op.os_name and not self.op.force:
2702
      CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
2703
                     self.op.force_variant)
2704
      instance_os = self.op.os_name
2705
    else:
2706
      instance_os = instance.os
2707

    
2708
    assert not (self.op.disk_template and self.op.disks), \
2709
      "Can't modify disk template and apply disk changes at the same time"
2710

    
2711
    if self.op.disk_template:
2712
      self._PreCheckDiskTemplate(pnode_info)
2713

    
2714
    # hvparams processing
2715
    if self.op.hvparams:
2716
      hv_type = instance.hypervisor
2717
      i_hvdict = GetUpdatedParams(instance.hvparams, self.op.hvparams)
2718
      utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
2719
      hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
2720

    
2721
      # local check
2722
      hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
2723
      CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
2724
      self.hv_proposed = self.hv_new = hv_new # the new actual values
2725
      self.hv_inst = i_hvdict # the new dict (without defaults)
2726
    else:
2727
      self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
2728
                                              instance.hvparams)
2729
      self.hv_new = self.hv_inst = {}
2730

    
2731
    # beparams processing
2732
    if self.op.beparams:
2733
      i_bedict = GetUpdatedParams(instance.beparams, self.op.beparams,
2734
                                  use_none=True)
2735
      objects.UpgradeBeParams(i_bedict)
2736
      utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
2737
      be_new = cluster.SimpleFillBE(i_bedict)
2738
      self.be_proposed = self.be_new = be_new # the new actual values
2739
      self.be_inst = i_bedict # the new dict (without defaults)
2740
    else:
2741
      self.be_new = self.be_inst = {}
2742
      self.be_proposed = cluster.SimpleFillBE(instance.beparams)
2743
    be_old = cluster.FillBE(instance)
2744

    
2745
    # CPU param validation -- checking every time a parameter is
2746
    # changed to cover all cases where either CPU mask or vcpus have
2747
    # changed
2748
    if (constants.BE_VCPUS in self.be_proposed and
2749
        constants.HV_CPU_MASK in self.hv_proposed):
2750
      cpu_list = \
2751
        utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
2752
      # Verify mask is consistent with number of vCPUs. Can skip this
2753
      # test if only 1 entry in the CPU mask, which means same mask
2754
      # is applied to all vCPUs.
2755
      if (len(cpu_list) > 1 and
2756
          len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
2757
        raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
2758
                                   " CPU mask [%s]" %
2759
                                   (self.be_proposed[constants.BE_VCPUS],
2760
                                    self.hv_proposed[constants.HV_CPU_MASK]),
2761
                                   errors.ECODE_INVAL)
2762

    
2763
      # Only perform this test if a new CPU mask is given
2764
      if constants.HV_CPU_MASK in self.hv_new:
2765
        # Calculate the largest CPU number requested
2766
        max_requested_cpu = max(map(max, cpu_list))
2767
        # Check that all of the instance's nodes have enough physical CPUs to
2768
        # satisfy the requested CPU mask
2769
        _CheckNodesPhysicalCPUs(self, instance.all_nodes,
2770
                                max_requested_cpu + 1, instance.hypervisor)
2771

    
2772
    # osparams processing
2773
    if self.op.osparams:
2774
      i_osdict = GetUpdatedParams(instance.osparams, self.op.osparams)
2775
      CheckOSParams(self, True, nodelist, instance_os, i_osdict)
2776
      self.os_inst = i_osdict # the new dict (without defaults)
2777
    else:
2778
      self.os_inst = {}
2779

    
2780
    #TODO(dynmem): do the appropriate check involving MINMEM
2781
    if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
2782
        be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
2783
      mem_check_list = [pnode]
2784
      if be_new[constants.BE_AUTO_BALANCE]:
2785
        # either we changed auto_balance to yes or it was from before
2786
        mem_check_list.extend(instance.secondary_nodes)
2787
      instance_info = self.rpc.call_instance_info(pnode, instance.name,
2788
                                                  instance.hypervisor)
2789
      nodeinfo = self.rpc.call_node_info(mem_check_list, None,
2790
                                         [instance.hypervisor], False)
2791
      pninfo = nodeinfo[pnode]
2792
      msg = pninfo.fail_msg
2793
      if msg:
2794
        # Assume the primary node is unreachable and go ahead
2795
        self.warn.append("Can't get info from primary node %s: %s" %
2796
                         (pnode, msg))
2797
      else:
2798
        (_, _, (pnhvinfo, )) = pninfo.payload
2799
        if not isinstance(pnhvinfo.get("memory_free", None), int):
2800
          self.warn.append("Node data from primary node %s doesn't contain"
2801
                           " free memory information" % pnode)
2802
        elif instance_info.fail_msg:
2803
          self.warn.append("Can't get instance runtime information: %s" %
2804
                           instance_info.fail_msg)
2805
        else:
2806
          if instance_info.payload:
2807
            current_mem = int(instance_info.payload["memory"])
2808
          else:
2809
            # Assume instance not running
2810
            # (there is a slight race condition here, but it's not very
2811
            # probable, and we have no other way to check)
2812
            # TODO: Describe race condition
2813
            current_mem = 0
2814
          #TODO(dynmem): do the appropriate check involving MINMEM
2815
          miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
2816
                      pnhvinfo["memory_free"])
2817
          if miss_mem > 0:
2818
            raise errors.OpPrereqError("This change will prevent the instance"
2819
                                       " from starting, due to %d MB of memory"
2820
                                       " missing on its primary node" %
2821
                                       miss_mem, errors.ECODE_NORES)
2822

    
2823
      if be_new[constants.BE_AUTO_BALANCE]:
2824
        for node, nres in nodeinfo.items():
2825
          if node not in instance.secondary_nodes:
2826
            continue
2827
          nres.Raise("Can't get info from secondary node %s" % node,
2828
                     prereq=True, ecode=errors.ECODE_STATE)
2829
          (_, _, (nhvinfo, )) = nres.payload
2830
          if not isinstance(nhvinfo.get("memory_free", None), int):
2831
            raise errors.OpPrereqError("Secondary node %s didn't return free"
2832
                                       " memory information" % node,
2833
                                       errors.ECODE_STATE)
2834
          #TODO(dynmem): do the appropriate check involving MINMEM
2835
          elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
2836
            raise errors.OpPrereqError("This change will prevent the instance"
2837
                                       " from failover to its secondary node"
2838
                                       " %s, due to not enough memory" % node,
2839
                                       errors.ECODE_STATE)
2840

    
2841
    if self.op.runtime_mem:
2842
      remote_info = self.rpc.call_instance_info(instance.primary_node,
2843
                                                instance.name,
2844
                                                instance.hypervisor)
2845
      remote_info.Raise("Error checking node %s" % instance.primary_node)
2846
      if not remote_info.payload: # not running already
2847
        raise errors.OpPrereqError("Instance %s is not running" %
2848
                                   instance.name, errors.ECODE_STATE)
2849

    
2850
      current_memory = remote_info.payload["memory"]
2851
      if (not self.op.force and
2852
           (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
2853
            self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
2854
        raise errors.OpPrereqError("Instance %s must have memory between %d"
2855
                                   " and %d MB of memory unless --force is"
2856
                                   " given" %
2857
                                   (instance.name,
2858
                                    self.be_proposed[constants.BE_MINMEM],
2859
                                    self.be_proposed[constants.BE_MAXMEM]),
2860
                                   errors.ECODE_INVAL)
2861

    
2862
      delta = self.op.runtime_mem - current_memory
2863
      if delta > 0:
2864
        CheckNodeFreeMemory(self, instance.primary_node,
2865
                            "ballooning memory for instance %s" %
2866
                            instance.name, delta, instance.hypervisor)
2867

    
2868
    if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
2869
      raise errors.OpPrereqError("Disk operations not supported for"
2870
                                 " diskless instances", errors.ECODE_INVAL)
2871

    
2872
    def _PrepareNicCreate(_, params, private):
2873
      self._PrepareNicModification(params, private, None, None,
2874
                                   {}, cluster, pnode)
2875
      return (None, None)
2876

    
2877
    def _PrepareNicMod(_, nic, params, private):
2878
      self._PrepareNicModification(params, private, nic.ip, nic.network,
2879
                                   nic.nicparams, cluster, pnode)
2880
      return None
2881

    
2882
    def _PrepareNicRemove(_, params, __):
2883
      ip = params.ip
2884
      net = params.network
2885
      if net is not None and ip is not None:
2886
        self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
2887

    
2888
    # Verify NIC changes (operating on copy)
2889
    nics = instance.nics[:]
2890
    _ApplyContainerMods("NIC", nics, None, self.nicmod,
2891
                        _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
2892
    if len(nics) > constants.MAX_NICS:
2893
      raise errors.OpPrereqError("Instance has too many network interfaces"
2894
                                 " (%d), cannot add more" % constants.MAX_NICS,
2895
                                 errors.ECODE_STATE)
2896

    
2897
    def _PrepareDiskMod(_, disk, params, __):
2898
      disk.name = params.get(constants.IDISK_NAME, None)
2899

    
2900
    # Verify disk changes (operating on a copy)
2901
    disks = copy.deepcopy(instance.disks)
2902
    _ApplyContainerMods("disk", disks, None, self.diskmod, None,
2903
                        _PrepareDiskMod, None)
2904
    utils.ValidateDeviceNames("disk", disks)
2905
    if len(disks) > constants.MAX_DISKS:
2906
      raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
2907
                                 " more" % constants.MAX_DISKS,
2908
                                 errors.ECODE_STATE)
2909
    disk_sizes = [disk.size for disk in instance.disks]
2910
    disk_sizes.extend(params["size"] for (op, idx, params, private) in
2911
                      self.diskmod if op == constants.DDM_ADD)
2912
    ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
2913
    ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
2914

    
2915
    if self.op.offline is not None and self.op.offline:
2916
      CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE,
2917
                         msg="can't change to offline")
2918

    
2919
    # Pre-compute NIC changes (necessary to use result in hooks)
2920
    self._nic_chgdesc = []
2921
    if self.nicmod:
2922
      # Operate on copies as this is still in prereq
2923
      nics = [nic.Copy() for nic in instance.nics]
2924
      _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
2925
                          self._CreateNewNic, self._ApplyNicMods, None)
2926
      # Verify that NIC names are unique and valid
2927
      utils.ValidateDeviceNames("NIC", nics)
2928
      self._new_nics = nics
2929
      ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
2930
    else:
2931
      self._new_nics = None
2932
      ispec[constants.ISPEC_NIC_COUNT] = len(instance.nics)
2933

    
2934
    if not self.op.ignore_ipolicy:
2935
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2936
                                                              group_info)
2937

    
2938
      # Fill ispec with backend parameters
2939
      ispec[constants.ISPEC_SPINDLE_USE] = \
2940
        self.be_new.get(constants.BE_SPINDLE_USE, None)
2941
      ispec[constants.ISPEC_CPU_COUNT] = self.be_new.get(constants.BE_VCPUS,
2942
                                                         None)
2943

    
2944
      # Copy ispec to verify parameters with min/max values separately
2945
      if self.op.disk_template:
2946
        new_disk_template = self.op.disk_template
2947
      else:
2948
        new_disk_template = instance.disk_template
2949
      ispec_max = ispec.copy()
2950
      ispec_max[constants.ISPEC_MEM_SIZE] = \
2951
        self.be_new.get(constants.BE_MAXMEM, None)
2952
      res_max = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_max,
2953
                                                     new_disk_template)
2954
      ispec_min = ispec.copy()
2955
      ispec_min[constants.ISPEC_MEM_SIZE] = \
2956
        self.be_new.get(constants.BE_MINMEM, None)
2957
      res_min = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
2958
                                                     new_disk_template)
2959

    
2960
      if (res_max or res_min):
2961
        # FIXME: Improve error message by including information about whether
2962
        # the upper or lower limit of the parameter fails the ipolicy.
2963
        msg = ("Instance allocation to group %s (%s) violates policy: %s" %
2964
               (group_info, group_info.name,
2965
                utils.CommaJoin(set(res_max + res_min))))
2966
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2967

    
2968
  def _ConvertPlainToDrbd(self, feedback_fn):
2969
    """Converts an instance from plain to drbd.
2970

2971
    """
2972
    feedback_fn("Converting template to drbd")
2973
    instance = self.instance
2974
    pnode = instance.primary_node
2975
    snode = self.op.remote_node
2976

    
2977
    assert instance.disk_template == constants.DT_PLAIN
2978

    
2979
    # create a fake disk info for _GenerateDiskTemplate
2980
    disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
2981
                  constants.IDISK_VG: d.logical_id[0],
2982
                  constants.IDISK_NAME: d.name}
2983
                 for d in instance.disks]
2984
    new_disks = GenerateDiskTemplate(self, self.op.disk_template,
2985
                                     instance.name, pnode, [snode],
2986
                                     disk_info, None, None, 0, feedback_fn,
2987
                                     self.diskparams)
2988
    anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
2989
                                        self.diskparams)
2990
    p_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, pnode)
2991
    s_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, snode)
2992
    info = GetInstanceInfoText(instance)
2993
    feedback_fn("Creating additional volumes...")
2994
    # first, create the missing data and meta devices
2995
    for disk in anno_disks:
2996
      # unfortunately this is... not too nice
2997
      CreateSingleBlockDev(self, pnode, instance, disk.children[1],
2998
                           info, True, p_excl_stor)
2999
      for child in disk.children:
3000
        CreateSingleBlockDev(self, snode, instance, child, info, True,
3001
                             s_excl_stor)
3002
    # at this stage, all new LVs have been created, we can rename the
3003
    # old ones
3004
    feedback_fn("Renaming original volumes...")
3005
    rename_list = [(o, n.children[0].logical_id)
3006
                   for (o, n) in zip(instance.disks, new_disks)]
3007
    result = self.rpc.call_blockdev_rename(pnode, rename_list)
3008
    result.Raise("Failed to rename original LVs")
3009

    
3010
    feedback_fn("Initializing DRBD devices...")
3011
    # all child devices are in place, we can now create the DRBD devices
3012
    try:
3013
      for disk in anno_disks:
3014
        for (node, excl_stor) in [(pnode, p_excl_stor), (snode, s_excl_stor)]:
3015
          f_create = node == pnode
3016
          CreateSingleBlockDev(self, node, instance, disk, info, f_create,
3017
                               excl_stor)
3018
    except errors.GenericError, e:
3019
      feedback_fn("Initializing of DRBD devices failed;"
3020
                  " renaming back original volumes...")
3021
      for disk in new_disks:
3022
        self.cfg.SetDiskID(disk, pnode)
3023
      rename_back_list = [(n.children[0], o.logical_id)
3024
                          for (n, o) in zip(new_disks, instance.disks)]
3025
      result = self.rpc.call_blockdev_rename(pnode, rename_back_list)
3026
      result.Raise("Failed to rename LVs back after error %s" % str(e))
3027
      raise
3028

    
3029
    # at this point, the instance has been modified
3030
    instance.disk_template = constants.DT_DRBD8
3031
    instance.disks = new_disks
3032
    self.cfg.Update(instance, feedback_fn)
3033

    
3034
    # Release node locks while waiting for sync
3035
    ReleaseLocks(self, locking.LEVEL_NODE)
3036

    
3037
    # disks are created, waiting for sync
3038
    disk_abort = not WaitForSync(self, instance,
3039
                                 oneshot=not self.op.wait_for_sync)
3040
    if disk_abort:
3041
      raise errors.OpExecError("There are some degraded disks for"
3042
                               " this instance, please cleanup manually")
3043

    
3044
    # Node resource locks will be released by caller
3045

    
3046
  def _ConvertDrbdToPlain(self, feedback_fn):
3047
    """Converts an instance from drbd to plain.
3048

3049
    """
3050
    instance = self.instance
3051

    
3052
    assert len(instance.secondary_nodes) == 1
3053
    assert instance.disk_template == constants.DT_DRBD8
3054

    
3055
    pnode = instance.primary_node
3056
    snode = instance.secondary_nodes[0]
3057
    feedback_fn("Converting template to plain")
3058

    
3059
    old_disks = AnnotateDiskParams(instance, instance.disks, self.cfg)
3060
    new_disks = [d.children[0] for d in instance.disks]
3061

    
3062
    # copy over size, mode and name
3063
    for parent, child in zip(old_disks, new_disks):
3064
      child.size = parent.size
3065
      child.mode = parent.mode
3066
      child.name = parent.name
3067

    
3068
    # this is a DRBD disk, return its port to the pool
3069
    # NOTE: this must be done right before the call to cfg.Update!
3070
    for disk in old_disks:
3071
      tcp_port = disk.logical_id[2]
3072
      self.cfg.AddTcpUdpPort(tcp_port)
3073

    
3074
    # update instance structure
3075
    instance.disks = new_disks
3076
    instance.disk_template = constants.DT_PLAIN
3077
    _UpdateIvNames(0, instance.disks)
3078
    self.cfg.Update(instance, feedback_fn)
3079

    
3080
    # Release locks in case removing disks takes a while
3081
    ReleaseLocks(self, locking.LEVEL_NODE)
3082

    
3083
    feedback_fn("Removing volumes on the secondary node...")
3084
    for disk in old_disks:
3085
      self.cfg.SetDiskID(disk, snode)
3086
      msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
3087
      if msg:
3088
        self.LogWarning("Could not remove block device %s on node %s,"
3089
                        " continuing anyway: %s", disk.iv_name, snode, msg)
3090

    
3091
    feedback_fn("Removing unneeded volumes on the primary node...")
3092
    for idx, disk in enumerate(old_disks):
3093
      meta = disk.children[1]
3094
      self.cfg.SetDiskID(meta, pnode)
3095
      msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
3096
      if msg:
3097
        self.LogWarning("Could not remove metadata for disk %d on node %s,"
3098
                        " continuing anyway: %s", idx, pnode, msg)
3099

    
3100
  def _CreateNewDisk(self, idx, params, _):
3101
    """Creates a new disk.
3102

3103
    """
3104
    instance = self.instance
3105

    
3106
    # add a new disk
3107
    if instance.disk_template in constants.DTS_FILEBASED:
3108
      (file_driver, file_path) = instance.disks[0].logical_id
3109
      file_path = os.path.dirname(file_path)
3110
    else:
3111
      file_driver = file_path = None
3112

    
3113
    disk = \
3114
      GenerateDiskTemplate(self, instance.disk_template, instance.name,
3115
                           instance.primary_node, instance.secondary_nodes,
3116
                           [params], file_path, file_driver, idx,
3117
                           self.Log, self.diskparams)[0]
3118

    
3119
    new_disks = CreateDisks(self, instance, disks=[disk])
3120

    
3121
    if self.cluster.prealloc_wipe_disks:
3122
      # Wipe new disk
3123
      WipeOrCleanupDisks(self, instance,
3124
                         disks=[(idx, disk, 0)],
3125
                         cleanup=new_disks)
3126

    
3127
    return (disk, [
3128
      ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
3129
      ])
3130

    
3131
  @staticmethod
3132
  def _ModifyDisk(idx, disk, params, _):
3133
    """Modifies a disk.
3134

3135
    """
3136
    changes = []
3137
    mode = params.get(constants.IDISK_MODE, None)
3138
    if mode:
3139
      disk.mode = mode
3140
      changes.append(("disk.mode/%d" % idx, disk.mode))
3141

    
3142
    name = params.get(constants.IDISK_NAME, None)
3143
    disk.name = name
3144
    changes.append(("disk.name/%d" % idx, disk.name))
3145

    
3146
    return changes
3147

    
3148
  def _RemoveDisk(self, idx, root, _):
3149
    """Removes a disk.
3150

3151
    """
3152
    (anno_disk,) = AnnotateDiskParams(self.instance, [root], self.cfg)
3153
    for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
3154
      self.cfg.SetDiskID(disk, node)
3155
      msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
3156
      if msg:
3157
        self.LogWarning("Could not remove disk/%d on node '%s': %s,"
3158
                        " continuing anyway", idx, node, msg)
3159

    
3160
    # if this is a DRBD disk, return its port to the pool
3161
    if root.dev_type in constants.LDS_DRBD:
3162
      self.cfg.AddTcpUdpPort(root.logical_id[2])
3163

    
3164
  def _CreateNewNic(self, idx, params, private):
3165
    """Creates data structure for a new network interface.
3166

3167
    """
3168
    mac = params[constants.INIC_MAC]
3169
    ip = params.get(constants.INIC_IP, None)
3170
    net = params.get(constants.INIC_NETWORK, None)
3171
    name = params.get(constants.INIC_NAME, None)
3172
    net_uuid = self.cfg.LookupNetwork(net)
3173
    #TODO: not private.filled?? can a nic have no nicparams??
3174
    nicparams = private.filled
3175
    nobj = objects.NIC(mac=mac, ip=ip, network=net_uuid, name=name,
3176
                       nicparams=nicparams)
3177
    nobj.uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
3178

    
3179
    return (nobj, [
3180
      ("nic.%d" % idx,
3181
       "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" %
3182
       (mac, ip, private.filled[constants.NIC_MODE],
3183
       private.filled[constants.NIC_LINK],
3184
       net)),
3185
      ])
3186

    
3187
  def _ApplyNicMods(self, idx, nic, params, private):
3188
    """Modifies a network interface.
3189

3190
    """
3191
    changes = []
3192

    
3193
    for key in [constants.INIC_MAC, constants.INIC_IP, constants.INIC_NAME]:
3194
      if key in params:
3195
        changes.append(("nic.%s/%d" % (key, idx), params[key]))
3196
        setattr(nic, key, params[key])
3197

    
3198
    new_net = params.get(constants.INIC_NETWORK, nic.network)
3199
    new_net_uuid = self.cfg.LookupNetwork(new_net)
3200
    if new_net_uuid != nic.network:
3201
      changes.append(("nic.network/%d" % idx, new_net))
3202
      nic.network = new_net_uuid
3203

    
3204
    if private.filled:
3205
      nic.nicparams = private.filled
3206

    
3207
      for (key, val) in nic.nicparams.items():
3208
        changes.append(("nic.%s/%d" % (key, idx), val))
3209

    
3210
    return changes
3211

    
3212
  def Exec(self, feedback_fn):
3213
    """Modifies an instance.
3214

3215
    All parameters take effect only at the next restart of the instance.
3216

3217
    """
3218
    # Process here the warnings from CheckPrereq, as we don't have a
3219
    # feedback_fn there.
3220
    # TODO: Replace with self.LogWarning
3221
    for warn in self.warn:
3222
      feedback_fn("WARNING: %s" % warn)
3223

    
3224
    assert ((self.op.disk_template is None) ^
3225
            bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
3226
      "Not owning any node resource locks"
3227

    
3228
    result = []
3229
    instance = self.instance
3230

    
3231
    # New primary node
3232
    if self.op.pnode:
3233
      instance.primary_node = self.op.pnode
3234

    
3235
    # runtime memory
3236
    if self.op.runtime_mem:
3237
      rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
3238
                                                     instance,
3239
                                                     self.op.runtime_mem)
3240
      rpcres.Raise("Cannot modify instance runtime memory")
3241
      result.append(("runtime_memory", self.op.runtime_mem))
3242

    
3243
    # Apply disk changes
3244
    _ApplyContainerMods("disk", instance.disks, result, self.diskmod,
3245
                        self._CreateNewDisk, self._ModifyDisk,
3246
                        self._RemoveDisk)
3247
    _UpdateIvNames(0, instance.disks)
3248

    
3249
    if self.op.disk_template:
3250
      if __debug__:
3251
        check_nodes = set(instance.all_nodes)
3252
        if self.op.remote_node:
3253
          check_nodes.add(self.op.remote_node)
3254
        for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
3255
          owned = self.owned_locks(level)
3256
          assert not (check_nodes - owned), \
3257
            ("Not owning the correct locks, owning %r, expected at least %r" %
3258
             (owned, check_nodes))
3259

    
3260
      r_shut = ShutdownInstanceDisks(self, instance)
3261
      if not r_shut:
3262
        raise errors.OpExecError("Cannot shutdown instance disks, unable to"
3263
                                 " proceed with disk template conversion")
3264
      mode = (instance.disk_template, self.op.disk_template)
3265
      try:
3266
        self._DISK_CONVERSIONS[mode](self, feedback_fn)
3267
      except:
3268
        self.cfg.ReleaseDRBDMinors(instance.name)
3269
        raise
3270
      result.append(("disk_template", self.op.disk_template))
3271

    
3272
      assert instance.disk_template == self.op.disk_template, \
3273
        ("Expected disk template '%s', found '%s'" %
3274
         (self.op.disk_template, instance.disk_template))
3275

    
3276
    # Release node and resource locks if there are any (they might already have
3277
    # been released during disk conversion)
3278
    ReleaseLocks(self, locking.LEVEL_NODE)
3279
    ReleaseLocks(self, locking.LEVEL_NODE_RES)
3280

    
3281
    # Apply NIC changes
3282
    if self._new_nics is not None:
3283
      instance.nics = self._new_nics
3284
      result.extend(self._nic_chgdesc)
3285

    
3286
    # hvparams changes
3287
    if self.op.hvparams:
3288
      instance.hvparams = self.hv_inst
3289
      for key, val in self.op.hvparams.iteritems():
3290
        result.append(("hv/%s" % key, val))
3291

    
3292
    # beparams changes
3293
    if self.op.beparams:
3294
      instance.beparams = self.be_inst
3295
      for key, val in self.op.beparams.iteritems():
3296
        result.append(("be/%s" % key, val))
3297

    
3298
    # OS change
3299
    if self.op.os_name:
3300
      instance.os = self.op.os_name
3301

    
3302
    # osparams changes
3303
    if self.op.osparams:
3304
      instance.osparams = self.os_inst
3305
      for key, val in self.op.osparams.iteritems():
3306
        result.append(("os/%s" % key, val))
3307

    
3308
    if self.op.offline is None:
3309
      # Ignore
3310
      pass
3311
    elif self.op.offline:
3312
      # Mark instance as offline
3313
      self.cfg.MarkInstanceOffline(instance.name)
3314
      result.append(("admin_state", constants.ADMINST_OFFLINE))
3315
    else:
3316
      # Mark instance as online, but stopped
3317
      self.cfg.MarkInstanceDown(instance.name)
3318
      result.append(("admin_state", constants.ADMINST_DOWN))
3319

    
3320
    self.cfg.Update(instance, feedback_fn, self.proc.GetECId())
3321

    
3322
    assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
3323
                self.owned_locks(locking.LEVEL_NODE)), \
3324
      "All node locks should have been released by now"
3325

    
3326
    return result
3327

    
3328
  _DISK_CONVERSIONS = {
3329
    (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
3330
    (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
3331
    }
3332

    
3333

    
3334
class LUInstanceChangeGroup(LogicalUnit):
3335
  HPATH = "instance-change-group"
3336
  HTYPE = constants.HTYPE_INSTANCE
3337
  REQ_BGL = False
3338

    
3339
  def ExpandNames(self):
3340
    self.share_locks = ShareAll()
3341

    
3342
    self.needed_locks = {
3343
      locking.LEVEL_NODEGROUP: [],
3344
      locking.LEVEL_NODE: [],
3345
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
3346
      }
3347

    
3348
    self._ExpandAndLockInstance()
3349

    
3350
    if self.op.target_groups:
3351
      self.req_target_uuids = map(self.cfg.LookupNodeGroup,
3352
                                  self.op.target_groups)
3353
    else:
3354
      self.req_target_uuids = None
3355

    
3356
    self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
3357

    
3358
  def DeclareLocks(self, level):
3359
    if level == locking.LEVEL_NODEGROUP:
3360
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3361

    
3362
      if self.req_target_uuids:
3363
        lock_groups = set(self.req_target_uuids)
3364

    
3365
        # Lock all groups used by instance optimistically; this requires going
3366
        # via the node before it's locked, requiring verification later on
3367
        instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
3368
        lock_groups.update(instance_groups)
3369
      else:
3370
        # No target groups, need to lock all of them
3371
        lock_groups = locking.ALL_SET
3372

    
3373
      self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
3374

    
3375
    elif level == locking.LEVEL_NODE:
3376
      if self.req_target_uuids:
3377
        # Lock all nodes used by instances
3378
        self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3379
        self._LockInstancesNodes()
3380

    
3381
        # Lock all nodes in all potential target groups
3382
        lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
3383
                       self.cfg.GetInstanceNodeGroups(self.op.instance_name))
3384
        member_nodes = [node_name
3385
                        for group in lock_groups
3386
                        for node_name in self.cfg.GetNodeGroup(group).members]
3387
        self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3388
      else:
3389
        # Lock all nodes as all groups are potential targets
3390
        self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3391

    
3392
  def CheckPrereq(self):
3393
    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3394
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3395
    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3396

    
3397
    assert (self.req_target_uuids is None or
3398
            owned_groups.issuperset(self.req_target_uuids))
3399
    assert owned_instances == set([self.op.instance_name])
3400

    
3401
    # Get instance information
3402
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3403

    
3404
    # Check if node groups for locked instance are still correct
3405
    assert owned_nodes.issuperset(self.instance.all_nodes), \
3406
      ("Instance %s's nodes changed while we kept the lock" %
3407
       self.op.instance_name)
3408

    
3409
    inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
3410
                                          owned_groups)
3411

    
3412
    if self.req_target_uuids:
3413
      # User requested specific target groups
3414
      self.target_uuids = frozenset(self.req_target_uuids)
3415
    else:
3416
      # All groups except those used by the instance are potential targets
3417
      self.target_uuids = owned_groups - inst_groups
3418

    
3419
    conflicting_groups = self.target_uuids & inst_groups
3420
    if conflicting_groups:
3421
      raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
3422
                                 " used by the instance '%s'" %
3423
                                 (utils.CommaJoin(conflicting_groups),
3424
                                  self.op.instance_name),
3425
                                 errors.ECODE_INVAL)
3426

    
3427
    if not self.target_uuids:
3428
      raise errors.OpPrereqError("There are no possible target groups",
3429
                                 errors.ECODE_INVAL)
3430

    
3431
  def BuildHooksEnv(self):
3432
    """Build hooks env.
3433

3434
    """
3435
    assert self.target_uuids
3436

    
3437
    env = {
3438
      "TARGET_GROUPS": " ".join(self.target_uuids),
3439
      }
3440

    
3441
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
3442

    
3443
    return env
3444

    
3445
  def BuildHooksNodes(self):
3446
    """Build hooks nodes.
3447

3448
    """
3449
    mn = self.cfg.GetMasterNode()
3450
    return ([mn], [mn])
3451

    
3452
  def Exec(self, feedback_fn):
3453
    instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
3454

    
3455
    assert instances == [self.op.instance_name], "Instance not locked"
3456

    
3457
    req = iallocator.IAReqGroupChange(instances=instances,
3458
                                      target_groups=list(self.target_uuids))
3459
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
3460

    
3461
    ial.Run(self.op.iallocator)
3462

    
3463
    if not ial.success:
3464
      raise errors.OpPrereqError("Can't compute solution for changing group of"
3465
                                 " instance '%s' using iallocator '%s': %s" %
3466
                                 (self.op.instance_name, self.op.iallocator,
3467
                                  ial.info), errors.ECODE_NORES)
3468

    
3469
    jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
3470

    
3471
    self.LogInfo("Iallocator returned %s job(s) for changing group of"
3472
                 " instance '%s'", len(jobs), self.op.instance_name)
3473

    
3474
    return ResultWithJobs(jobs)