Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance.py @ 96ed3a3e

History | View | Annotate | Download (146.1 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 pathutils
40
from ganeti import rpc
41
from ganeti import utils
42

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

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

    
66
import ganeti.masterd.instance
67

    
68

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

    
77

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

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

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

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

    
98

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

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

    
108

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

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

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

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

    
135

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

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

142
  @return: The fully filled beparams
143

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

    
153

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

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

163
  @returns: The build up nics
164

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

    
173
    net = nic.get(constants.INIC_NETWORK, None)
174
    link = nic.get(constants.NIC_LINK, None)
175
    ip = nic.get(constants.INIC_IP, None)
176
    vlan = nic.get(constants.INIC_VLAN, None)
177

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

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

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

    
208
      nic_ip = ip
209

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

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

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

    
228
    #  Build nic parameters
229
    nicparams = {}
230
    if nic_mode_req:
231
      nicparams[constants.NIC_MODE] = nic_mode
232
    if link:
233
      nicparams[constants.NIC_LINK] = link
234
    if vlan:
235
      nicparams[constants.NIC_VLAN] = vlan
236

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

    
248
  return nics
249

    
250

    
251
def _CheckForConflictingIp(lu, ip, node_uuid):
252
  """In case of conflicting IP address raise error.
253

254
  @type ip: string
255
  @param ip: IP address
256
  @type node_uuid: string
257
  @param node_uuid: node UUID
258

259
  """
260
  (conf_net, _) = lu.cfg.CheckIPInNodeGroup(ip, node_uuid)
261
  if conf_net is not None:
262
    raise errors.OpPrereqError(("The requested IP address (%s) belongs to"
263
                                " network %s, but the target NIC does not." %
264
                                (ip, conf_net)),
265
                               errors.ECODE_STATE)
266

    
267
  return (None, None)
268

    
269

    
270
def _ComputeIPolicyInstanceSpecViolation(
271
  ipolicy, instance_spec, disk_template,
272
  _compute_fn=ComputeIPolicySpecViolation):
273
  """Compute if instance specs meets the specs of ipolicy.
274

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

284
  """
285
  mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
286
  cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
287
  disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
288
  disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
289
  nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
290
  spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
291

    
292
  return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
293
                     disk_sizes, spindle_use, disk_template)
294

    
295

    
296
def _CheckOSVariant(os_obj, name):
297
  """Check whether an OS name conforms to the os variants specification.
298

299
  @type os_obj: L{objects.OS}
300
  @param os_obj: OS object to check
301
  @type name: string
302
  @param name: OS name passed by the user, to check for validity
303

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

    
316
  if variant not in os_obj.supported_variants:
317
    raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
318

    
319

    
320
class LUInstanceCreate(LogicalUnit):
321
  """Create an instance.
322

323
  """
324
  HPATH = "instance-add"
325
  HTYPE = constants.HTYPE_INSTANCE
326
  REQ_BGL = False
327

    
328
  def _CheckDiskTemplateValid(self):
329
    """Checks validity of disk template.
330

331
    """
332
    cluster = self.cfg.GetClusterInfo()
333
    if self.op.disk_template is None:
334
      # FIXME: It would be better to take the default disk template from the
335
      # ipolicy, but for the ipolicy we need the primary node, which we get from
336
      # the iallocator, which wants the disk template as input. To solve this
337
      # chicken-and-egg problem, it should be possible to specify just a node
338
      # group from the iallocator and take the ipolicy from that.
339
      self.op.disk_template = cluster.enabled_disk_templates[0]
340
    CheckDiskTemplateEnabled(cluster, self.op.disk_template)
341

    
342
  def _CheckDiskArguments(self):
343
    """Checks validity of disk-related arguments.
344

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

    
349
    self._CheckDiskTemplateValid()
350

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

    
382
    self.adopt_disks = has_adopt
383

    
384
  def _CheckVLANArguments(self):
385
    """ Check validity of VLANs if given
386

387
    """
388
    for nic in self.op.nics:
389
      vlan = nic.get(constants.INIC_VLAN, None)
390
      if vlan:
391
        if vlan[0] == ".":
392
          # vlan starting with dot means single untagged vlan,
393
          # might be followed by trunk (:)
394
          if not vlan[1:].isdigit():
395
            vlanlist = vlan[1:].split(':')
396
            for vl in vlanlist:
397
              if not vl.isdigit():
398
                raise errors.OpPrereqError("Specified VLAN parameter is "
399
                                           "invalid : %s" % vlan,
400
                                             errors.ECODE_INVAL)
401
        elif vlan[0] == ":":
402
          # Trunk - tagged only
403
          vlanlist = vlan[1:].split(':')
404
          for vl in vlanlist:
405
            if not vl.isdigit():
406
              raise errors.OpPrereqError("Specified VLAN parameter is invalid"
407
                                           " : %s" % vlan, errors.ECODE_INVAL)
408
        elif vlan.isdigit():
409
          # This is the simplest case. No dots, only single digit
410
          # -> Create untagged access port, dot needs to be added
411
          nic[constants.INIC_VLAN] = "." + vlan
412
        else:
413
          raise errors.OpPrereqError("Specified VLAN parameter is invalid"
414
                                       " : %s" % vlan, errors.ECODE_INVAL)
415

    
416
  def CheckArguments(self):
417
    """Check arguments.
418

419
    """
420
    # do not require name_check to ease forward/backward compatibility
421
    # for tools
422
    if self.op.no_install and self.op.start:
423
      self.LogInfo("No-installation mode selected, disabling startup")
424
      self.op.start = False
425
    # validate/normalize the instance name
426
    self.op.instance_name = \
427
      netutils.Hostname.GetNormalizedName(self.op.instance_name)
428

    
429
    if self.op.ip_check and not self.op.name_check:
430
      # TODO: make the ip check more flexible and not depend on the name check
431
      raise errors.OpPrereqError("Cannot do IP address check without a name"
432
                                 " check", errors.ECODE_INVAL)
433

    
434
    # check nics' parameter names
435
    for nic in self.op.nics:
436
      utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
437
    # check that NIC's parameters names are unique and valid
438
    utils.ValidateDeviceNames("NIC", self.op.nics)
439

    
440
    self._CheckVLANArguments()
441

    
442
    self._CheckDiskArguments()
443
    assert self.op.disk_template is not None
444

    
445
    # instance name verification
446
    if self.op.name_check:
447
      self.hostname = _CheckHostnameSane(self, self.op.instance_name)
448
      self.op.instance_name = self.hostname.name
449
      # used in CheckPrereq for ip ping check
450
      self.check_ip = self.hostname.ip
451
    else:
452
      self.check_ip = None
453

    
454
    # file storage checks
455
    if (self.op.file_driver and
456
        not self.op.file_driver in constants.FILE_DRIVER):
457
      raise errors.OpPrereqError("Invalid file driver name '%s'" %
458
                                 self.op.file_driver, errors.ECODE_INVAL)
459

    
460
    # set default file_driver if unset and required
461
    if (not self.op.file_driver and
462
        self.op.disk_template in [constants.DT_FILE,
463
                                  constants.DT_SHARED_FILE]):
464
      self.op.file_driver = constants.FD_LOOP
465

    
466
    ### Node/iallocator related checks
467
    CheckIAllocatorOrNode(self, "iallocator", "pnode")
468

    
469
    if self.op.pnode is not None:
470
      if self.op.disk_template in constants.DTS_INT_MIRROR:
471
        if self.op.snode is None:
472
          raise errors.OpPrereqError("The networked disk templates need"
473
                                     " a mirror node", errors.ECODE_INVAL)
474
      elif self.op.snode:
475
        self.LogWarning("Secondary node will be ignored on non-mirrored disk"
476
                        " template")
477
        self.op.snode = None
478

    
479
    _CheckOpportunisticLocking(self.op)
480

    
481
    if self.op.mode == constants.INSTANCE_IMPORT:
482
      # On import force_variant must be True, because if we forced it at
483
      # initial install, our only chance when importing it back is that it
484
      # works again!
485
      self.op.force_variant = True
486

    
487
      if self.op.no_install:
488
        self.LogInfo("No-installation mode has no effect during import")
489

    
490
    elif self.op.mode == constants.INSTANCE_CREATE:
491
      if self.op.os_type is None:
492
        raise errors.OpPrereqError("No guest OS specified",
493
                                   errors.ECODE_INVAL)
494
      if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
495
        raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
496
                                   " installation" % self.op.os_type,
497
                                   errors.ECODE_STATE)
498
    elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
499
      self._cds = GetClusterDomainSecret()
500

    
501
      # Check handshake to ensure both clusters have the same domain secret
502
      src_handshake = self.op.source_handshake
503
      if not src_handshake:
504
        raise errors.OpPrereqError("Missing source handshake",
505
                                   errors.ECODE_INVAL)
506

    
507
      errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
508
                                                           src_handshake)
509
      if errmsg:
510
        raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
511
                                   errors.ECODE_INVAL)
512

    
513
      # Load and check source CA
514
      self.source_x509_ca_pem = self.op.source_x509_ca
515
      if not self.source_x509_ca_pem:
516
        raise errors.OpPrereqError("Missing source X509 CA",
517
                                   errors.ECODE_INVAL)
518

    
519
      try:
520
        (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
521
                                                    self._cds)
522
      except OpenSSL.crypto.Error, err:
523
        raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
524
                                   (err, ), errors.ECODE_INVAL)
525

    
526
      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
527
      if errcode is not None:
528
        raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
529
                                   errors.ECODE_INVAL)
530

    
531
      self.source_x509_ca = cert
532

    
533
      src_instance_name = self.op.source_instance_name
534
      if not src_instance_name:
535
        raise errors.OpPrereqError("Missing source instance name",
536
                                   errors.ECODE_INVAL)
537

    
538
      self.source_instance_name = \
539
        netutils.GetHostname(name=src_instance_name).name
540

    
541
    else:
542
      raise errors.OpPrereqError("Invalid instance creation mode %r" %
543
                                 self.op.mode, errors.ECODE_INVAL)
544

    
545
  def ExpandNames(self):
546
    """ExpandNames for CreateInstance.
547

548
    Figure out the right locks for instance creation.
549

550
    """
551
    self.needed_locks = {}
552

    
553
    # this is just a preventive check, but someone might still add this
554
    # instance in the meantime, and creation will fail at lock-add time
555
    if self.op.instance_name in\
556
      [inst.name for inst in self.cfg.GetAllInstancesInfo().values()]:
557
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
558
                                 self.op.instance_name, errors.ECODE_EXISTS)
559

    
560
    self.add_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
561

    
562
    if self.op.iallocator:
563
      # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
564
      # specifying a group on instance creation and then selecting nodes from
565
      # that group
566
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
567
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
568

    
569
      if self.op.opportunistic_locking:
570
        self.opportunistic_locks[locking.LEVEL_NODE] = True
571
        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
572
    else:
573
      (self.op.pnode_uuid, self.op.pnode) = \
574
        ExpandNodeUuidAndName(self.cfg, self.op.pnode_uuid, self.op.pnode)
575
      nodelist = [self.op.pnode_uuid]
576
      if self.op.snode is not None:
577
        (self.op.snode_uuid, self.op.snode) = \
578
          ExpandNodeUuidAndName(self.cfg, self.op.snode_uuid, self.op.snode)
579
        nodelist.append(self.op.snode_uuid)
580
      self.needed_locks[locking.LEVEL_NODE] = nodelist
581

    
582
    # in case of import lock the source node too
583
    if self.op.mode == constants.INSTANCE_IMPORT:
584
      src_node = self.op.src_node
585
      src_path = self.op.src_path
586

    
587
      if src_path is None:
588
        self.op.src_path = src_path = self.op.instance_name
589

    
590
      if src_node is None:
591
        self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
592
        self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
593
        self.op.src_node = None
594
        if os.path.isabs(src_path):
595
          raise errors.OpPrereqError("Importing an instance from a path"
596
                                     " requires a source node option",
597
                                     errors.ECODE_INVAL)
598
      else:
599
        (self.op.src_node_uuid, self.op.src_node) = (_, src_node) = \
600
          ExpandNodeUuidAndName(self.cfg, self.op.src_node_uuid, src_node)
601
        if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
602
          self.needed_locks[locking.LEVEL_NODE].append(self.op.src_node_uuid)
603
        if not os.path.isabs(src_path):
604
          self.op.src_path = \
605
            utils.PathJoin(pathutils.EXPORT_DIR, src_path)
606

    
607
    self.needed_locks[locking.LEVEL_NODE_RES] = \
608
      CopyLockList(self.needed_locks[locking.LEVEL_NODE])
609

    
610
  def _RunAllocator(self):
611
    """Run the allocator based on input opcode.
612

613
    """
614
    if self.op.opportunistic_locking:
615
      # Only consider nodes for which a lock is held
616
      node_name_whitelist = self.cfg.GetNodeNames(
617
        self.owned_locks(locking.LEVEL_NODE))
618
    else:
619
      node_name_whitelist = None
620

    
621
    req = _CreateInstanceAllocRequest(self.op, self.disks,
622
                                      self.nics, self.be_full,
623
                                      node_name_whitelist)
624
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
625

    
626
    ial.Run(self.op.iallocator)
627

    
628
    if not ial.success:
629
      # When opportunistic locks are used only a temporary failure is generated
630
      if self.op.opportunistic_locking:
631
        ecode = errors.ECODE_TEMP_NORES
632
      else:
633
        ecode = errors.ECODE_NORES
634

    
635
      raise errors.OpPrereqError("Can't compute nodes using"
636
                                 " iallocator '%s': %s" %
637
                                 (self.op.iallocator, ial.info),
638
                                 ecode)
639

    
640
    (self.op.pnode_uuid, self.op.pnode) = \
641
      ExpandNodeUuidAndName(self.cfg, None, ial.result[0])
642
    self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
643
                 self.op.instance_name, self.op.iallocator,
644
                 utils.CommaJoin(ial.result))
645

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

    
648
    if req.RequiredNodes() == 2:
649
      (self.op.snode_uuid, self.op.snode) = \
650
        ExpandNodeUuidAndName(self.cfg, None, ial.result[1])
651

    
652
  def BuildHooksEnv(self):
653
    """Build hooks env.
654

655
    This runs on master, primary and secondary nodes of the instance.
656

657
    """
658
    env = {
659
      "ADD_MODE": self.op.mode,
660
      }
661
    if self.op.mode == constants.INSTANCE_IMPORT:
662
      env["SRC_NODE"] = self.op.src_node
663
      env["SRC_PATH"] = self.op.src_path
664
      env["SRC_IMAGES"] = self.src_images
665

    
666
    env.update(BuildInstanceHookEnv(
667
      name=self.op.instance_name,
668
      primary_node_name=self.op.pnode,
669
      secondary_node_names=self.cfg.GetNodeNames(self.secondaries),
670
      status=self.op.start,
671
      os_type=self.op.os_type,
672
      minmem=self.be_full[constants.BE_MINMEM],
673
      maxmem=self.be_full[constants.BE_MAXMEM],
674
      vcpus=self.be_full[constants.BE_VCPUS],
675
      nics=NICListToTuple(self, self.nics),
676
      disk_template=self.op.disk_template,
677
      disks=[(d[constants.IDISK_NAME], d.get("uuid", ""),
678
              d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
679
             for d in self.disks],
680
      bep=self.be_full,
681
      hvp=self.hv_full,
682
      hypervisor_name=self.op.hypervisor,
683
      tags=self.op.tags,
684
      ))
685

    
686
    return env
687

    
688
  def BuildHooksNodes(self):
689
    """Build hooks nodes.
690

691
    """
692
    nl = [self.cfg.GetMasterNode(), self.op.pnode_uuid] + self.secondaries
693
    return nl, nl
694

    
695
  def _ReadExportInfo(self):
696
    """Reads the export information from disk.
697

698
    It will override the opcode source node and path with the actual
699
    information, if these two were not specified before.
700

701
    @return: the export information
702

703
    """
704
    assert self.op.mode == constants.INSTANCE_IMPORT
705

    
706
    if self.op.src_node_uuid is None:
707
      locked_nodes = self.owned_locks(locking.LEVEL_NODE)
708
      exp_list = self.rpc.call_export_list(locked_nodes)
709
      found = False
710
      for node_uuid in exp_list:
711
        if exp_list[node_uuid].fail_msg:
712
          continue
713
        if self.op.src_path in exp_list[node_uuid].payload:
714
          found = True
715
          self.op.src_node = self.cfg.GetNodeInfo(node_uuid).name
716
          self.op.src_node_uuid = node_uuid
717
          self.op.src_path = utils.PathJoin(pathutils.EXPORT_DIR,
718
                                            self.op.src_path)
719
          break
720
      if not found:
721
        raise errors.OpPrereqError("No export found for relative path %s" %
722
                                   self.op.src_path, errors.ECODE_INVAL)
723

    
724
    CheckNodeOnline(self, self.op.src_node_uuid)
725
    result = self.rpc.call_export_info(self.op.src_node_uuid, self.op.src_path)
726
    result.Raise("No export or invalid export found in dir %s" %
727
                 self.op.src_path)
728

    
729
    export_info = objects.SerializableConfigParser.Loads(str(result.payload))
730
    if not export_info.has_section(constants.INISECT_EXP):
731
      raise errors.ProgrammerError("Corrupted export config",
732
                                   errors.ECODE_ENVIRON)
733

    
734
    ei_version = export_info.get(constants.INISECT_EXP, "version")
735
    if int(ei_version) != constants.EXPORT_VERSION:
736
      raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
737
                                 (ei_version, constants.EXPORT_VERSION),
738
                                 errors.ECODE_ENVIRON)
739
    return export_info
740

    
741
  def _ReadExportParams(self, einfo):
742
    """Use export parameters as defaults.
743

744
    In case the opcode doesn't specify (as in override) some instance
745
    parameters, then try to use them from the export information, if
746
    that declares them.
747

748
    """
749
    self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
750

    
751
    if not self.op.disks:
752
      disks = []
753
      # TODO: import the disk iv_name too
754
      for idx in range(constants.MAX_DISKS):
755
        if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
756
          disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
757
          disks.append({constants.IDISK_SIZE: disk_sz})
758
      self.op.disks = disks
759
      if not disks and self.op.disk_template != constants.DT_DISKLESS:
760
        raise errors.OpPrereqError("No disk info specified and the export"
761
                                   " is missing the disk information",
762
                                   errors.ECODE_INVAL)
763

    
764
    if not self.op.nics:
765
      nics = []
766
      for idx in range(constants.MAX_NICS):
767
        if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
768
          ndict = {}
769
          for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
770
            nic_param_name = "nic%d_%s" % (idx, name)
771
            if einfo.has_option(constants.INISECT_INS, nic_param_name):
772
              v = einfo.get(constants.INISECT_INS, nic_param_name)
773
              ndict[name] = v
774
          nics.append(ndict)
775
        else:
776
          break
777
      self.op.nics = nics
778

    
779
    if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
780
      self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
781

    
782
    if (self.op.hypervisor is None and
783
        einfo.has_option(constants.INISECT_INS, "hypervisor")):
784
      self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
785

    
786
    if einfo.has_section(constants.INISECT_HYP):
787
      # use the export parameters but do not override the ones
788
      # specified by the user
789
      for name, value in einfo.items(constants.INISECT_HYP):
790
        if name not in self.op.hvparams:
791
          self.op.hvparams[name] = value
792

    
793
    if einfo.has_section(constants.INISECT_BEP):
794
      # use the parameters, without overriding
795
      for name, value in einfo.items(constants.INISECT_BEP):
796
        if name not in self.op.beparams:
797
          self.op.beparams[name] = value
798
        # Compatibility for the old "memory" be param
799
        if name == constants.BE_MEMORY:
800
          if constants.BE_MAXMEM not in self.op.beparams:
801
            self.op.beparams[constants.BE_MAXMEM] = value
802
          if constants.BE_MINMEM not in self.op.beparams:
803
            self.op.beparams[constants.BE_MINMEM] = value
804
    else:
805
      # try to read the parameters old style, from the main section
806
      for name in constants.BES_PARAMETERS:
807
        if (name not in self.op.beparams and
808
            einfo.has_option(constants.INISECT_INS, name)):
809
          self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
810

    
811
    if einfo.has_section(constants.INISECT_OSP):
812
      # use the parameters, without overriding
813
      for name, value in einfo.items(constants.INISECT_OSP):
814
        if name not in self.op.osparams:
815
          self.op.osparams[name] = value
816

    
817
  def _RevertToDefaults(self, cluster):
818
    """Revert the instance parameters to the default values.
819

820
    """
821
    # hvparams
822
    hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
823
    for name in self.op.hvparams.keys():
824
      if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
825
        del self.op.hvparams[name]
826
    # beparams
827
    be_defs = cluster.SimpleFillBE({})
828
    for name in self.op.beparams.keys():
829
      if name in be_defs and be_defs[name] == self.op.beparams[name]:
830
        del self.op.beparams[name]
831
    # nic params
832
    nic_defs = cluster.SimpleFillNIC({})
833
    for nic in self.op.nics:
834
      for name in constants.NICS_PARAMETERS:
835
        if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
836
          del nic[name]
837
    # osparams
838
    os_defs = cluster.SimpleFillOS(self.op.os_type, {})
839
    for name in self.op.osparams.keys():
840
      if name in os_defs and os_defs[name] == self.op.osparams[name]:
841
        del self.op.osparams[name]
842

    
843
  def _CalculateFileStorageDir(self):
844
    """Calculate final instance file storage dir.
845

846
    """
847
    # file storage dir calculation/check
848
    self.instance_file_storage_dir = None
849
    if self.op.disk_template in constants.DTS_FILEBASED:
850
      # build the full file storage dir path
851
      joinargs = []
852

    
853
      if self.op.disk_template == constants.DT_SHARED_FILE:
854
        get_fsd_fn = self.cfg.GetSharedFileStorageDir
855
      else:
856
        get_fsd_fn = self.cfg.GetFileStorageDir
857

    
858
      cfg_storagedir = get_fsd_fn()
859
      if not cfg_storagedir:
860
        raise errors.OpPrereqError("Cluster file storage dir not defined",
861
                                   errors.ECODE_STATE)
862
      joinargs.append(cfg_storagedir)
863

    
864
      if self.op.file_storage_dir is not None:
865
        joinargs.append(self.op.file_storage_dir)
866

    
867
      joinargs.append(self.op.instance_name)
868

    
869
      # pylint: disable=W0142
870
      self.instance_file_storage_dir = utils.PathJoin(*joinargs)
871

    
872
  def CheckPrereq(self): # pylint: disable=R0914
873
    """Check prerequisites.
874

875
    """
876
    self._CalculateFileStorageDir()
877

    
878
    if self.op.mode == constants.INSTANCE_IMPORT:
879
      export_info = self._ReadExportInfo()
880
      self._ReadExportParams(export_info)
881
      self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
882
    else:
883
      self._old_instance_name = None
884

    
885
    if (not self.cfg.GetVGName() and
886
        self.op.disk_template not in constants.DTS_NOT_LVM):
887
      raise errors.OpPrereqError("Cluster does not support lvm-based"
888
                                 " instances", errors.ECODE_STATE)
889

    
890
    if (self.op.hypervisor is None or
891
        self.op.hypervisor == constants.VALUE_AUTO):
892
      self.op.hypervisor = self.cfg.GetHypervisorType()
893

    
894
    cluster = self.cfg.GetClusterInfo()
895
    enabled_hvs = cluster.enabled_hypervisors
896
    if self.op.hypervisor not in enabled_hvs:
897
      raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
898
                                 " cluster (%s)" %
899
                                 (self.op.hypervisor, ",".join(enabled_hvs)),
900
                                 errors.ECODE_STATE)
901

    
902
    # Check tag validity
903
    for tag in self.op.tags:
904
      objects.TaggableObject.ValidateTag(tag)
905

    
906
    # check hypervisor parameter syntax (locally)
907
    utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
908
    filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
909
                                      self.op.hvparams)
910
    hv_type = hypervisor.GetHypervisorClass(self.op.hypervisor)
911
    hv_type.CheckParameterSyntax(filled_hvp)
912
    self.hv_full = filled_hvp
913
    # check that we don't specify global parameters on an instance
914
    CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, "hypervisor",
915
                         "instance", "cluster")
916

    
917
    # fill and remember the beparams dict
918
    self.be_full = _ComputeFullBeParams(self.op, cluster)
919

    
920
    # build os parameters
921
    self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
922

    
923
    # now that hvp/bep are in final format, let's reset to defaults,
924
    # if told to do so
925
    if self.op.identify_defaults:
926
      self._RevertToDefaults(cluster)
927

    
928
    # NIC buildup
929
    self.nics = _ComputeNics(self.op, cluster, self.check_ip, self.cfg,
930
                             self.proc.GetECId())
931

    
932
    # disk checks/pre-build
933
    default_vg = self.cfg.GetVGName()
934
    self.disks = ComputeDisks(self.op, default_vg)
935

    
936
    if self.op.mode == constants.INSTANCE_IMPORT:
937
      disk_images = []
938
      for idx in range(len(self.disks)):
939
        option = "disk%d_dump" % idx
940
        if export_info.has_option(constants.INISECT_INS, option):
941
          # FIXME: are the old os-es, disk sizes, etc. useful?
942
          export_name = export_info.get(constants.INISECT_INS, option)
943
          image = utils.PathJoin(self.op.src_path, export_name)
944
          disk_images.append(image)
945
        else:
946
          disk_images.append(False)
947

    
948
      self.src_images = disk_images
949

    
950
      if self.op.instance_name == self._old_instance_name:
951
        for idx, nic in enumerate(self.nics):
952
          if nic.mac == constants.VALUE_AUTO:
953
            nic_mac_ini = "nic%d_mac" % idx
954
            nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
955

    
956
    # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
957

    
958
    # ip ping checks (we use the same ip that was resolved in ExpandNames)
959
    if self.op.ip_check:
960
      if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
961
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
962
                                   (self.check_ip, self.op.instance_name),
963
                                   errors.ECODE_NOTUNIQUE)
964

    
965
    #### mac address generation
966
    # By generating here the mac address both the allocator and the hooks get
967
    # the real final mac address rather than the 'auto' or 'generate' value.
968
    # There is a race condition between the generation and the instance object
969
    # creation, which means that we know the mac is valid now, but we're not
970
    # sure it will be when we actually add the instance. If things go bad
971
    # adding the instance will abort because of a duplicate mac, and the
972
    # creation job will fail.
973
    for nic in self.nics:
974
      if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
975
        nic.mac = self.cfg.GenerateMAC(nic.network, self.proc.GetECId())
976

    
977
    #### allocator run
978

    
979
    if self.op.iallocator is not None:
980
      self._RunAllocator()
981

    
982
    # Release all unneeded node locks
983
    keep_locks = filter(None, [self.op.pnode_uuid, self.op.snode_uuid,
984
                               self.op.src_node_uuid])
985
    ReleaseLocks(self, locking.LEVEL_NODE, keep=keep_locks)
986
    ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=keep_locks)
987
    ReleaseLocks(self, locking.LEVEL_NODE_ALLOC)
988

    
989
    assert (self.owned_locks(locking.LEVEL_NODE) ==
990
            self.owned_locks(locking.LEVEL_NODE_RES)), \
991
      "Node locks differ from node resource locks"
992

    
993
    #### node related checks
994

    
995
    # check primary node
996
    self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode_uuid)
997
    assert self.pnode is not None, \
998
      "Cannot retrieve locked node %s" % self.op.pnode_uuid
999
    if pnode.offline:
1000
      raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
1001
                                 pnode.name, errors.ECODE_STATE)
1002
    if pnode.drained:
1003
      raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
1004
                                 pnode.name, errors.ECODE_STATE)
1005
    if not pnode.vm_capable:
1006
      raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
1007
                                 " '%s'" % pnode.name, errors.ECODE_STATE)
1008

    
1009
    self.secondaries = []
1010

    
1011
    # Fill in any IPs from IP pools. This must happen here, because we need to
1012
    # know the nic's primary node, as specified by the iallocator
1013
    for idx, nic in enumerate(self.nics):
1014
      net_uuid = nic.network
1015
      if net_uuid is not None:
1016
        nobj = self.cfg.GetNetwork(net_uuid)
1017
        netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.uuid)
1018
        if netparams is None:
1019
          raise errors.OpPrereqError("No netparams found for network"
1020
                                     " %s. Probably not connected to"
1021
                                     " node's %s nodegroup" %
1022
                                     (nobj.name, self.pnode.name),
1023
                                     errors.ECODE_INVAL)
1024
        self.LogInfo("NIC/%d inherits netparams %s" %
1025
                     (idx, netparams.values()))
1026
        nic.nicparams = dict(netparams)
1027
        if nic.ip is not None:
1028
          if nic.ip.lower() == constants.NIC_IP_POOL:
1029
            try:
1030
              nic.ip = self.cfg.GenerateIp(net_uuid, self.proc.GetECId())
1031
            except errors.ReservationError:
1032
              raise errors.OpPrereqError("Unable to get a free IP for NIC %d"
1033
                                         " from the address pool" % idx,
1034
                                         errors.ECODE_STATE)
1035
            self.LogInfo("Chose IP %s from network %s", nic.ip, nobj.name)
1036
          else:
1037
            try:
1038
              self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId(),
1039
                                 check=self.op.conflicts_check)
1040
            except errors.ReservationError:
1041
              raise errors.OpPrereqError("IP address %s already in use"
1042
                                         " or does not belong to network %s" %
1043
                                         (nic.ip, nobj.name),
1044
                                         errors.ECODE_NOTUNIQUE)
1045

    
1046
      # net is None, ip None or given
1047
      elif self.op.conflicts_check:
1048
        _CheckForConflictingIp(self, nic.ip, self.pnode.uuid)
1049

    
1050
    # mirror node verification
1051
    if self.op.disk_template in constants.DTS_INT_MIRROR:
1052
      if self.op.snode_uuid == pnode.uuid:
1053
        raise errors.OpPrereqError("The secondary node cannot be the"
1054
                                   " primary node", errors.ECODE_INVAL)
1055
      CheckNodeOnline(self, self.op.snode_uuid)
1056
      CheckNodeNotDrained(self, self.op.snode_uuid)
1057
      CheckNodeVmCapable(self, self.op.snode_uuid)
1058
      self.secondaries.append(self.op.snode_uuid)
1059

    
1060
      snode = self.cfg.GetNodeInfo(self.op.snode_uuid)
1061
      if pnode.group != snode.group:
1062
        self.LogWarning("The primary and secondary nodes are in two"
1063
                        " different node groups; the disk parameters"
1064
                        " from the first disk's node group will be"
1065
                        " used")
1066

    
1067
    nodes = [pnode]
1068
    if self.op.disk_template in constants.DTS_INT_MIRROR:
1069
      nodes.append(snode)
1070
    has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
1071
    excl_stor = compat.any(map(has_es, nodes))
1072
    if excl_stor and not self.op.disk_template in constants.DTS_EXCL_STORAGE:
1073
      raise errors.OpPrereqError("Disk template %s not supported with"
1074
                                 " exclusive storage" % self.op.disk_template,
1075
                                 errors.ECODE_STATE)
1076
    for disk in self.disks:
1077
      CheckSpindlesExclusiveStorage(disk, excl_stor, True)
1078

    
1079
    node_uuids = [pnode.uuid] + self.secondaries
1080

    
1081
    if not self.adopt_disks:
1082
      if self.op.disk_template == constants.DT_RBD:
1083
        # _CheckRADOSFreeSpace() is just a placeholder.
1084
        # Any function that checks prerequisites can be placed here.
1085
        # Check if there is enough space on the RADOS cluster.
1086
        CheckRADOSFreeSpace()
1087
      elif self.op.disk_template == constants.DT_EXT:
1088
        # FIXME: Function that checks prereqs if needed
1089
        pass
1090
      elif self.op.disk_template in constants.DTS_LVM:
1091
        # Check lv size requirements, if not adopting
1092
        req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
1093
        CheckNodesFreeDiskPerVG(self, node_uuids, req_sizes)
1094
      else:
1095
        # FIXME: add checks for other, non-adopting, non-lvm disk templates
1096
        pass
1097

    
1098
    elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
1099
      all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
1100
                                disk[constants.IDISK_ADOPT])
1101
                     for disk in self.disks])
1102
      if len(all_lvs) != len(self.disks):
1103
        raise errors.OpPrereqError("Duplicate volume names given for adoption",
1104
                                   errors.ECODE_INVAL)
1105
      for lv_name in all_lvs:
1106
        try:
1107
          # FIXME: lv_name here is "vg/lv" need to ensure that other calls
1108
          # to ReserveLV uses the same syntax
1109
          self.cfg.ReserveLV(lv_name, self.proc.GetECId())
1110
        except errors.ReservationError:
1111
          raise errors.OpPrereqError("LV named %s used by another instance" %
1112
                                     lv_name, errors.ECODE_NOTUNIQUE)
1113

    
1114
      vg_names = self.rpc.call_vg_list([pnode.uuid])[pnode.uuid]
1115
      vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
1116

    
1117
      node_lvs = self.rpc.call_lv_list([pnode.uuid],
1118
                                       vg_names.payload.keys())[pnode.uuid]
1119
      node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
1120
      node_lvs = node_lvs.payload
1121

    
1122
      delta = all_lvs.difference(node_lvs.keys())
1123
      if delta:
1124
        raise errors.OpPrereqError("Missing logical volume(s): %s" %
1125
                                   utils.CommaJoin(delta),
1126
                                   errors.ECODE_INVAL)
1127
      online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
1128
      if online_lvs:
1129
        raise errors.OpPrereqError("Online logical volumes found, cannot"
1130
                                   " adopt: %s" % utils.CommaJoin(online_lvs),
1131
                                   errors.ECODE_STATE)
1132
      # update the size of disk based on what is found
1133
      for dsk in self.disks:
1134
        dsk[constants.IDISK_SIZE] = \
1135
          int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
1136
                                        dsk[constants.IDISK_ADOPT])][0]))
1137

    
1138
    elif self.op.disk_template == constants.DT_BLOCK:
1139
      # Normalize and de-duplicate device paths
1140
      all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
1141
                       for disk in self.disks])
1142
      if len(all_disks) != len(self.disks):
1143
        raise errors.OpPrereqError("Duplicate disk names given for adoption",
1144
                                   errors.ECODE_INVAL)
1145
      baddisks = [d for d in all_disks
1146
                  if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
1147
      if baddisks:
1148
        raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
1149
                                   " cannot be adopted" %
1150
                                   (utils.CommaJoin(baddisks),
1151
                                    constants.ADOPTABLE_BLOCKDEV_ROOT),
1152
                                   errors.ECODE_INVAL)
1153

    
1154
      node_disks = self.rpc.call_bdev_sizes([pnode.uuid],
1155
                                            list(all_disks))[pnode.uuid]
1156
      node_disks.Raise("Cannot get block device information from node %s" %
1157
                       pnode.name)
1158
      node_disks = node_disks.payload
1159
      delta = all_disks.difference(node_disks.keys())
1160
      if delta:
1161
        raise errors.OpPrereqError("Missing block device(s): %s" %
1162
                                   utils.CommaJoin(delta),
1163
                                   errors.ECODE_INVAL)
1164
      for dsk in self.disks:
1165
        dsk[constants.IDISK_SIZE] = \
1166
          int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
1167

    
1168
    # Check disk access param to be compatible with specified hypervisor
1169
    node_info = self.cfg.GetNodeInfo(self.op.pnode_uuid)
1170
    node_group = self.cfg.GetNodeGroup(node_info.group)
1171
    disk_params = self.cfg.GetGroupDiskParams(node_group)
1172
    access_type = disk_params[self.op.disk_template].get(
1173
      constants.RBD_ACCESS, constants.DISK_KERNELSPACE
1174
    )
1175

    
1176
    if not IsValidDiskAccessModeCombination(self.op.hypervisor,
1177
                                            self.op.disk_template,
1178
                                            access_type):
1179
      raise errors.OpPrereqError("Selected hypervisor (%s) cannot be"
1180
                                 " used with %s disk access param" %
1181
                                 (self.op.hypervisor, access_type),
1182
                                  errors.ECODE_STATE)
1183

    
1184
    # Verify instance specs
1185
    spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
1186
    ispec = {
1187
      constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
1188
      constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
1189
      constants.ISPEC_DISK_COUNT: len(self.disks),
1190
      constants.ISPEC_DISK_SIZE: [disk[constants.IDISK_SIZE]
1191
                                  for disk in self.disks],
1192
      constants.ISPEC_NIC_COUNT: len(self.nics),
1193
      constants.ISPEC_SPINDLE_USE: spindle_use,
1194
      }
1195

    
1196
    group_info = self.cfg.GetNodeGroup(pnode.group)
1197
    ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1198
    res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec,
1199
                                               self.op.disk_template)
1200
    if not self.op.ignore_ipolicy and res:
1201
      msg = ("Instance allocation to group %s (%s) violates policy: %s" %
1202
             (pnode.group, group_info.name, utils.CommaJoin(res)))
1203
      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1204

    
1205
    CheckHVParams(self, node_uuids, self.op.hypervisor, self.op.hvparams)
1206

    
1207
    CheckNodeHasOS(self, pnode.uuid, self.op.os_type, self.op.force_variant)
1208
    # check OS parameters (remotely)
1209
    CheckOSParams(self, True, node_uuids, self.op.os_type, self.os_full)
1210

    
1211
    CheckNicsBridgesExist(self, self.nics, self.pnode.uuid)
1212

    
1213
    #TODO: _CheckExtParams (remotely)
1214
    # Check parameters for extstorage
1215

    
1216
    # memory check on primary node
1217
    #TODO(dynmem): use MINMEM for checking
1218
    if self.op.start:
1219
      hvfull = objects.FillDict(cluster.hvparams.get(self.op.hypervisor, {}),
1220
                                self.op.hvparams)
1221
      CheckNodeFreeMemory(self, self.pnode.uuid,
1222
                          "creating instance %s" % self.op.instance_name,
1223
                          self.be_full[constants.BE_MAXMEM],
1224
                          self.op.hypervisor, hvfull)
1225

    
1226
    self.dry_run_result = list(node_uuids)
1227

    
1228
  def Exec(self, feedback_fn):
1229
    """Create and add the instance to the cluster.
1230

1231
    """
1232
    assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
1233
                self.owned_locks(locking.LEVEL_NODE)), \
1234
      "Node locks differ from node resource locks"
1235
    assert not self.glm.is_owned(locking.LEVEL_NODE_ALLOC)
1236

    
1237
    ht_kind = self.op.hypervisor
1238
    if ht_kind in constants.HTS_REQ_PORT:
1239
      network_port = self.cfg.AllocatePort()
1240
    else:
1241
      network_port = None
1242

    
1243
    instance_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
1244

    
1245
    # This is ugly but we got a chicken-egg problem here
1246
    # We can only take the group disk parameters, as the instance
1247
    # has no disks yet (we are generating them right here).
1248
    nodegroup = self.cfg.GetNodeGroup(self.pnode.group)
1249
    disks = GenerateDiskTemplate(self,
1250
                                 self.op.disk_template,
1251
                                 instance_uuid, self.pnode.uuid,
1252
                                 self.secondaries,
1253
                                 self.disks,
1254
                                 self.instance_file_storage_dir,
1255
                                 self.op.file_driver,
1256
                                 0,
1257
                                 feedback_fn,
1258
                                 self.cfg.GetGroupDiskParams(nodegroup))
1259

    
1260
    iobj = objects.Instance(name=self.op.instance_name,
1261
                            uuid=instance_uuid,
1262
                            os=self.op.os_type,
1263
                            primary_node=self.pnode.uuid,
1264
                            nics=self.nics, disks=disks,
1265
                            disk_template=self.op.disk_template,
1266
                            disks_active=False,
1267
                            admin_state=constants.ADMINST_DOWN,
1268
                            network_port=network_port,
1269
                            beparams=self.op.beparams,
1270
                            hvparams=self.op.hvparams,
1271
                            hypervisor=self.op.hypervisor,
1272
                            osparams=self.op.osparams,
1273
                            )
1274

    
1275
    if self.op.tags:
1276
      for tag in self.op.tags:
1277
        iobj.AddTag(tag)
1278

    
1279
    if self.adopt_disks:
1280
      if self.op.disk_template == constants.DT_PLAIN:
1281
        # rename LVs to the newly-generated names; we need to construct
1282
        # 'fake' LV disks with the old data, plus the new unique_id
1283
        tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
1284
        rename_to = []
1285
        for t_dsk, a_dsk in zip(tmp_disks, self.disks):
1286
          rename_to.append(t_dsk.logical_id)
1287
          t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
1288
        result = self.rpc.call_blockdev_rename(self.pnode.uuid,
1289
                                               zip(tmp_disks, rename_to))
1290
        result.Raise("Failed to rename adoped LVs")
1291
    else:
1292
      feedback_fn("* creating instance disks...")
1293
      try:
1294
        CreateDisks(self, iobj)
1295
      except errors.OpExecError:
1296
        self.LogWarning("Device creation failed")
1297
        self.cfg.ReleaseDRBDMinors(self.op.instance_name)
1298
        raise
1299

    
1300
    feedback_fn("adding instance %s to cluster config" % self.op.instance_name)
1301

    
1302
    self.cfg.AddInstance(iobj, self.proc.GetECId())
1303

    
1304
    # Declare that we don't want to remove the instance lock anymore, as we've
1305
    # added the instance to the config
1306
    del self.remove_locks[locking.LEVEL_INSTANCE]
1307

    
1308
    if self.op.mode == constants.INSTANCE_IMPORT:
1309
      # Release unused nodes
1310
      ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node_uuid])
1311
    else:
1312
      # Release all nodes
1313
      ReleaseLocks(self, locking.LEVEL_NODE)
1314

    
1315
    disk_abort = False
1316
    if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
1317
      feedback_fn("* wiping instance disks...")
1318
      try:
1319
        WipeDisks(self, iobj)
1320
      except errors.OpExecError, err:
1321
        logging.exception("Wiping disks failed")
1322
        self.LogWarning("Wiping instance disks failed (%s)", err)
1323
        disk_abort = True
1324

    
1325
    if disk_abort:
1326
      # Something is already wrong with the disks, don't do anything else
1327
      pass
1328
    elif self.op.wait_for_sync:
1329
      disk_abort = not WaitForSync(self, iobj)
1330
    elif iobj.disk_template in constants.DTS_INT_MIRROR:
1331
      # make sure the disks are not degraded (still sync-ing is ok)
1332
      feedback_fn("* checking mirrors status")
1333
      disk_abort = not WaitForSync(self, iobj, oneshot=True)
1334
    else:
1335
      disk_abort = False
1336

    
1337
    if disk_abort:
1338
      RemoveDisks(self, iobj)
1339
      self.cfg.RemoveInstance(iobj.uuid)
1340
      # Make sure the instance lock gets removed
1341
      self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
1342
      raise errors.OpExecError("There are some degraded disks for"
1343
                               " this instance")
1344

    
1345
    # instance disks are now active
1346
    iobj.disks_active = True
1347

    
1348
    # Release all node resource locks
1349
    ReleaseLocks(self, locking.LEVEL_NODE_RES)
1350

    
1351
    if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
1352
      if self.op.mode == constants.INSTANCE_CREATE:
1353
        if not self.op.no_install:
1354
          pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
1355
                        not self.op.wait_for_sync)
1356
          if pause_sync:
1357
            feedback_fn("* pausing disk sync to install instance OS")
1358
            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
1359
                                                              (iobj.disks,
1360
                                                               iobj), True)
1361
            for idx, success in enumerate(result.payload):
1362
              if not success:
1363
                logging.warn("pause-sync of instance %s for disk %d failed",
1364
                             self.op.instance_name, idx)
1365

    
1366
          feedback_fn("* running the instance OS create scripts...")
1367
          # FIXME: pass debug option from opcode to backend
1368
          os_add_result = \
1369
            self.rpc.call_instance_os_add(self.pnode.uuid, (iobj, None), False,
1370
                                          self.op.debug_level)
1371
          if pause_sync:
1372
            feedback_fn("* resuming disk sync")
1373
            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
1374
                                                              (iobj.disks,
1375
                                                               iobj), False)
1376
            for idx, success in enumerate(result.payload):
1377
              if not success:
1378
                logging.warn("resume-sync of instance %s for disk %d failed",
1379
                             self.op.instance_name, idx)
1380

    
1381
          os_add_result.Raise("Could not add os for instance %s"
1382
                              " on node %s" % (self.op.instance_name,
1383
                                               self.pnode.name))
1384

    
1385
      else:
1386
        if self.op.mode == constants.INSTANCE_IMPORT:
1387
          feedback_fn("* running the instance OS import scripts...")
1388

    
1389
          transfers = []
1390

    
1391
          for idx, image in enumerate(self.src_images):
1392
            if not image:
1393
              continue
1394

    
1395
            # FIXME: pass debug option from opcode to backend
1396
            dt = masterd.instance.DiskTransfer("disk/%s" % idx,
1397
                                               constants.IEIO_FILE, (image, ),
1398
                                               constants.IEIO_SCRIPT,
1399
                                               ((iobj.disks[idx], iobj), idx),
1400
                                               None)
1401
            transfers.append(dt)
1402

    
1403
          import_result = \
1404
            masterd.instance.TransferInstanceData(self, feedback_fn,
1405
                                                  self.op.src_node_uuid,
1406
                                                  self.pnode.uuid,
1407
                                                  self.pnode.secondary_ip,
1408
                                                  iobj, transfers)
1409
          if not compat.all(import_result):
1410
            self.LogWarning("Some disks for instance %s on node %s were not"
1411
                            " imported successfully" % (self.op.instance_name,
1412
                                                        self.pnode.name))
1413

    
1414
          rename_from = self._old_instance_name
1415

    
1416
        elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
1417
          feedback_fn("* preparing remote import...")
1418
          # The source cluster will stop the instance before attempting to make
1419
          # a connection. In some cases stopping an instance can take a long
1420
          # time, hence the shutdown timeout is added to the connection
1421
          # timeout.
1422
          connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
1423
                             self.op.source_shutdown_timeout)
1424
          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
1425

    
1426
          assert iobj.primary_node == self.pnode.uuid
1427
          disk_results = \
1428
            masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
1429
                                          self.source_x509_ca,
1430
                                          self._cds, timeouts)
1431
          if not compat.all(disk_results):
1432
            # TODO: Should the instance still be started, even if some disks
1433
            # failed to import (valid for local imports, too)?
1434
            self.LogWarning("Some disks for instance %s on node %s were not"
1435
                            " imported successfully" % (self.op.instance_name,
1436
                                                        self.pnode.name))
1437

    
1438
          rename_from = self.source_instance_name
1439

    
1440
        else:
1441
          # also checked in the prereq part
1442
          raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
1443
                                       % self.op.mode)
1444

    
1445
        # Run rename script on newly imported instance
1446
        assert iobj.name == self.op.instance_name
1447
        feedback_fn("Running rename script for %s" % self.op.instance_name)
1448
        result = self.rpc.call_instance_run_rename(self.pnode.uuid, iobj,
1449
                                                   rename_from,
1450
                                                   self.op.debug_level)
1451
        result.Warn("Failed to run rename script for %s on node %s" %
1452
                    (self.op.instance_name, self.pnode.name), self.LogWarning)
1453

    
1454
    assert not self.owned_locks(locking.LEVEL_NODE_RES)
1455

    
1456
    if self.op.start:
1457
      iobj.admin_state = constants.ADMINST_UP
1458
      self.cfg.Update(iobj, feedback_fn)
1459
      logging.info("Starting instance %s on node %s", self.op.instance_name,
1460
                   self.pnode.name)
1461
      feedback_fn("* starting instance...")
1462
      result = self.rpc.call_instance_start(self.pnode.uuid, (iobj, None, None),
1463
                                            False, self.op.reason)
1464
      result.Raise("Could not start instance")
1465

    
1466
    return list(iobj.all_nodes)
1467

    
1468

    
1469
class LUInstanceRename(LogicalUnit):
1470
  """Rename an instance.
1471

1472
  """
1473
  HPATH = "instance-rename"
1474
  HTYPE = constants.HTYPE_INSTANCE
1475

    
1476
  def CheckArguments(self):
1477
    """Check arguments.
1478

1479
    """
1480
    if self.op.ip_check and not self.op.name_check:
1481
      # TODO: make the ip check more flexible and not depend on the name check
1482
      raise errors.OpPrereqError("IP address check requires a name check",
1483
                                 errors.ECODE_INVAL)
1484

    
1485
  def BuildHooksEnv(self):
1486
    """Build hooks env.
1487

1488
    This runs on master, primary and secondary nodes of the instance.
1489

1490
    """
1491
    env = BuildInstanceHookEnvByObject(self, self.instance)
1492
    env["INSTANCE_NEW_NAME"] = self.op.new_name
1493
    return env
1494

    
1495
  def BuildHooksNodes(self):
1496
    """Build hooks nodes.
1497

1498
    """
1499
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
1500
    return (nl, nl)
1501

    
1502
  def CheckPrereq(self):
1503
    """Check prerequisites.
1504

1505
    This checks that the instance is in the cluster and is not running.
1506

1507
    """
1508
    (self.op.instance_uuid, self.op.instance_name) = \
1509
      ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid,
1510
                                self.op.instance_name)
1511
    instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
1512
    assert instance is not None
1513

    
1514
    # It should actually not happen that an instance is running with a disabled
1515
    # disk template, but in case it does, the renaming of file-based instances
1516
    # will fail horribly. Thus, we test it before.
1517
    if (instance.disk_template in constants.DTS_FILEBASED and
1518
        self.op.new_name != instance.name):
1519
      CheckDiskTemplateEnabled(self.cfg.GetClusterInfo(),
1520
                               instance.disk_template)
1521

    
1522
    CheckNodeOnline(self, instance.primary_node)
1523
    CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
1524
                       msg="cannot rename")
1525
    self.instance = instance
1526

    
1527
    new_name = self.op.new_name
1528
    if self.op.name_check:
1529
      hostname = _CheckHostnameSane(self, new_name)
1530
      new_name = self.op.new_name = hostname.name
1531
      if (self.op.ip_check and
1532
          netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
1533
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
1534
                                   (hostname.ip, new_name),
1535
                                   errors.ECODE_NOTUNIQUE)
1536

    
1537
    instance_names = [inst.name for
1538
                      inst in self.cfg.GetAllInstancesInfo().values()]
1539
    if new_name in instance_names and new_name != instance.name:
1540
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
1541
                                 new_name, errors.ECODE_EXISTS)
1542

    
1543
  def Exec(self, feedback_fn):
1544
    """Rename the instance.
1545

1546
    """
1547
    old_name = self.instance.name
1548

    
1549
    rename_file_storage = False
1550
    if (self.instance.disk_template in constants.DTS_FILEBASED and
1551
        self.op.new_name != self.instance.name):
1552
      old_file_storage_dir = os.path.dirname(
1553
                               self.instance.disks[0].logical_id[1])
1554
      rename_file_storage = True
1555

    
1556
    self.cfg.RenameInstance(self.instance.uuid, self.op.new_name)
1557
    # Change the instance lock. This is definitely safe while we hold the BGL.
1558
    # Otherwise the new lock would have to be added in acquired mode.
1559
    assert self.REQ_BGL
1560
    assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER)
1561
    self.glm.remove(locking.LEVEL_INSTANCE, old_name)
1562
    self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
1563

    
1564
    # re-read the instance from the configuration after rename
1565
    renamed_inst = self.cfg.GetInstanceInfo(self.instance.uuid)
1566

    
1567
    if rename_file_storage:
1568
      new_file_storage_dir = os.path.dirname(
1569
                               renamed_inst.disks[0].logical_id[1])
1570
      result = self.rpc.call_file_storage_dir_rename(renamed_inst.primary_node,
1571
                                                     old_file_storage_dir,
1572
                                                     new_file_storage_dir)
1573
      result.Raise("Could not rename on node %s directory '%s' to '%s'"
1574
                   " (but the instance has been renamed in Ganeti)" %
1575
                   (self.cfg.GetNodeName(renamed_inst.primary_node),
1576
                    old_file_storage_dir, new_file_storage_dir))
1577

    
1578
    StartInstanceDisks(self, renamed_inst, None)
1579
    # update info on disks
1580
    info = GetInstanceInfoText(renamed_inst)
1581
    for (idx, disk) in enumerate(renamed_inst.disks):
1582
      for node_uuid in renamed_inst.all_nodes:
1583
        result = self.rpc.call_blockdev_setinfo(node_uuid,
1584
                                                (disk, renamed_inst), info)
1585
        result.Warn("Error setting info on node %s for disk %s" %
1586
                    (self.cfg.GetNodeName(node_uuid), idx), self.LogWarning)
1587
    try:
1588
      result = self.rpc.call_instance_run_rename(renamed_inst.primary_node,
1589
                                                 renamed_inst, old_name,
1590
                                                 self.op.debug_level)
1591
      result.Warn("Could not run OS rename script for instance %s on node %s"
1592
                  " (but the instance has been renamed in Ganeti)" %
1593
                  (renamed_inst.name,
1594
                   self.cfg.GetNodeName(renamed_inst.primary_node)),
1595
                  self.LogWarning)
1596
    finally:
1597
      ShutdownInstanceDisks(self, renamed_inst)
1598

    
1599
    return renamed_inst.name
1600

    
1601

    
1602
class LUInstanceRemove(LogicalUnit):
1603
  """Remove an instance.
1604

1605
  """
1606
  HPATH = "instance-remove"
1607
  HTYPE = constants.HTYPE_INSTANCE
1608
  REQ_BGL = False
1609

    
1610
  def ExpandNames(self):
1611
    self._ExpandAndLockInstance()
1612
    self.needed_locks[locking.LEVEL_NODE] = []
1613
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1614
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
1615

    
1616
  def DeclareLocks(self, level):
1617
    if level == locking.LEVEL_NODE:
1618
      self._LockInstancesNodes()
1619
    elif level == locking.LEVEL_NODE_RES:
1620
      # Copy node locks
1621
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1622
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1623

    
1624
  def BuildHooksEnv(self):
1625
    """Build hooks env.
1626

1627
    This runs on master, primary and secondary nodes of the instance.
1628

1629
    """
1630
    env = BuildInstanceHookEnvByObject(self, self.instance)
1631
    env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
1632
    return env
1633

    
1634
  def BuildHooksNodes(self):
1635
    """Build hooks nodes.
1636

1637
    """
1638
    nl = [self.cfg.GetMasterNode()]
1639
    nl_post = list(self.instance.all_nodes) + nl
1640
    return (nl, nl_post)
1641

    
1642
  def CheckPrereq(self):
1643
    """Check prerequisites.
1644

1645
    This checks that the instance is in the cluster.
1646

1647
    """
1648
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
1649
    assert self.instance is not None, \
1650
      "Cannot retrieve locked instance %s" % self.op.instance_name
1651

    
1652
  def Exec(self, feedback_fn):
1653
    """Remove the instance.
1654

1655
    """
1656
    logging.info("Shutting down instance %s on node %s", self.instance.name,
1657
                 self.cfg.GetNodeName(self.instance.primary_node))
1658

    
1659
    result = self.rpc.call_instance_shutdown(self.instance.primary_node,
1660
                                             self.instance,
1661
                                             self.op.shutdown_timeout,
1662
                                             self.op.reason)
1663
    if self.op.ignore_failures:
1664
      result.Warn("Warning: can't shutdown instance", feedback_fn)
1665
    else:
1666
      result.Raise("Could not shutdown instance %s on node %s" %
1667
                   (self.instance.name,
1668
                    self.cfg.GetNodeName(self.instance.primary_node)))
1669

    
1670
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1671
            self.owned_locks(locking.LEVEL_NODE_RES))
1672
    assert not (set(self.instance.all_nodes) -
1673
                self.owned_locks(locking.LEVEL_NODE)), \
1674
      "Not owning correct locks"
1675

    
1676
    RemoveInstance(self, feedback_fn, self.instance, self.op.ignore_failures)
1677

    
1678

    
1679
class LUInstanceMove(LogicalUnit):
1680
  """Move an instance by data-copying.
1681

1682
  """
1683
  HPATH = "instance-move"
1684
  HTYPE = constants.HTYPE_INSTANCE
1685
  REQ_BGL = False
1686

    
1687
  def ExpandNames(self):
1688
    self._ExpandAndLockInstance()
1689
    (self.op.target_node_uuid, self.op.target_node) = \
1690
      ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
1691
                            self.op.target_node)
1692
    self.needed_locks[locking.LEVEL_NODE] = [self.op.target_node_uuid]
1693
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1694
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
1695

    
1696
  def DeclareLocks(self, level):
1697
    if level == locking.LEVEL_NODE:
1698
      self._LockInstancesNodes(primary_only=True)
1699
    elif level == locking.LEVEL_NODE_RES:
1700
      # Copy node locks
1701
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1702
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1703

    
1704
  def BuildHooksEnv(self):
1705
    """Build hooks env.
1706

1707
    This runs on master, primary and secondary nodes of the instance.
1708

1709
    """
1710
    env = {
1711
      "TARGET_NODE": self.op.target_node,
1712
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
1713
      }
1714
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
1715
    return env
1716

    
1717
  def BuildHooksNodes(self):
1718
    """Build hooks nodes.
1719

1720
    """
1721
    nl = [
1722
      self.cfg.GetMasterNode(),
1723
      self.instance.primary_node,
1724
      self.op.target_node_uuid,
1725
      ]
1726
    return (nl, nl)
1727

    
1728
  def CheckPrereq(self):
1729
    """Check prerequisites.
1730

1731
    This checks that the instance is in the cluster.
1732

1733
    """
1734
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
1735
    assert self.instance is not None, \
1736
      "Cannot retrieve locked instance %s" % self.op.instance_name
1737

    
1738
    if self.instance.disk_template not in constants.DTS_COPYABLE:
1739
      raise errors.OpPrereqError("Disk template %s not suitable for copying" %
1740
                                 self.instance.disk_template,
1741
                                 errors.ECODE_STATE)
1742

    
1743
    target_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
1744
    assert target_node is not None, \
1745
      "Cannot retrieve locked node %s" % self.op.target_node
1746

    
1747
    self.target_node_uuid = target_node.uuid
1748
    if target_node.uuid == self.instance.primary_node:
1749
      raise errors.OpPrereqError("Instance %s is already on the node %s" %
1750
                                 (self.instance.name, target_node.name),
1751
                                 errors.ECODE_STATE)
1752

    
1753
    bep = self.cfg.GetClusterInfo().FillBE(self.instance)
1754

    
1755
    for idx, dsk in enumerate(self.instance.disks):
1756
      if dsk.dev_type not in (constants.DT_PLAIN, constants.DT_FILE,
1757
                              constants.DT_SHARED_FILE):
1758
        raise errors.OpPrereqError("Instance disk %d has a complex layout,"
1759
                                   " cannot copy" % idx, errors.ECODE_STATE)
1760

    
1761
    CheckNodeOnline(self, target_node.uuid)
1762
    CheckNodeNotDrained(self, target_node.uuid)
1763
    CheckNodeVmCapable(self, target_node.uuid)
1764
    cluster = self.cfg.GetClusterInfo()
1765
    group_info = self.cfg.GetNodeGroup(target_node.group)
1766
    ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1767
    CheckTargetNodeIPolicy(self, ipolicy, self.instance, target_node, self.cfg,
1768
                           ignore=self.op.ignore_ipolicy)
1769

    
1770
    if self.instance.admin_state == constants.ADMINST_UP:
1771
      # check memory requirements on the secondary node
1772
      CheckNodeFreeMemory(
1773
          self, target_node.uuid, "failing over instance %s" %
1774
          self.instance.name, bep[constants.BE_MAXMEM],
1775
          self.instance.hypervisor,
1776
          self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
1777
    else:
1778
      self.LogInfo("Not checking memory on the secondary node as"
1779
                   " instance will not be started")
1780

    
1781
    # check bridge existance
1782
    CheckInstanceBridgesExist(self, self.instance, node_uuid=target_node.uuid)
1783

    
1784
  def Exec(self, feedback_fn):
1785
    """Move an instance.
1786

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

1790
    """
1791
    source_node = self.cfg.GetNodeInfo(self.instance.primary_node)
1792
    target_node = self.cfg.GetNodeInfo(self.target_node_uuid)
1793

    
1794
    self.LogInfo("Shutting down instance %s on source node %s",
1795
                 self.instance.name, source_node.name)
1796

    
1797
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1798
            self.owned_locks(locking.LEVEL_NODE_RES))
1799

    
1800
    result = self.rpc.call_instance_shutdown(source_node.uuid, self.instance,
1801
                                             self.op.shutdown_timeout,
1802
                                             self.op.reason)
1803
    if self.op.ignore_consistency:
1804
      result.Warn("Could not shutdown instance %s on node %s. Proceeding"
1805
                  " anyway. Please make sure node %s is down. Error details" %
1806
                  (self.instance.name, source_node.name, source_node.name),
1807
                  self.LogWarning)
1808
    else:
1809
      result.Raise("Could not shutdown instance %s on node %s" %
1810
                   (self.instance.name, source_node.name))
1811

    
1812
    # create the target disks
1813
    try:
1814
      CreateDisks(self, self.instance, target_node_uuid=target_node.uuid)
1815
    except errors.OpExecError:
1816
      self.LogWarning("Device creation failed")
1817
      self.cfg.ReleaseDRBDMinors(self.instance.uuid)
1818
      raise
1819

    
1820
    cluster_name = self.cfg.GetClusterInfo().cluster_name
1821

    
1822
    errs = []
1823
    # activate, get path, copy the data over
1824
    for idx, disk in enumerate(self.instance.disks):
1825
      self.LogInfo("Copying data for disk %d", idx)
1826
      result = self.rpc.call_blockdev_assemble(
1827
                 target_node.uuid, (disk, self.instance), self.instance.name,
1828
                 True, idx)
1829
      if result.fail_msg:
1830
        self.LogWarning("Can't assemble newly created disk %d: %s",
1831
                        idx, result.fail_msg)
1832
        errs.append(result.fail_msg)
1833
        break
1834
      dev_path, _ = result.payload
1835
      result = self.rpc.call_blockdev_export(source_node.uuid, (disk,
1836
                                                                self.instance),
1837
                                             target_node.secondary_ip,
1838
                                             dev_path, cluster_name)
1839
      if result.fail_msg:
1840
        self.LogWarning("Can't copy data over for disk %d: %s",
1841
                        idx, result.fail_msg)
1842
        errs.append(result.fail_msg)
1843
        break
1844

    
1845
    if errs:
1846
      self.LogWarning("Some disks failed to copy, aborting")
1847
      try:
1848
        RemoveDisks(self, self.instance, target_node_uuid=target_node.uuid)
1849
      finally:
1850
        self.cfg.ReleaseDRBDMinors(self.instance.uuid)
1851
        raise errors.OpExecError("Errors during disk copy: %s" %
1852
                                 (",".join(errs),))
1853

    
1854
    self.instance.primary_node = target_node.uuid
1855
    self.cfg.Update(self.instance, feedback_fn)
1856

    
1857
    self.LogInfo("Removing the disks on the original node")
1858
    RemoveDisks(self, self.instance, target_node_uuid=source_node.uuid)
1859

    
1860
    # Only start the instance if it's marked as up
1861
    if self.instance.admin_state == constants.ADMINST_UP:
1862
      self.LogInfo("Starting instance %s on node %s",
1863
                   self.instance.name, target_node.name)
1864

    
1865
      disks_ok, _ = AssembleInstanceDisks(self, self.instance,
1866
                                          ignore_secondaries=True)
1867
      if not disks_ok:
1868
        ShutdownInstanceDisks(self, self.instance)
1869
        raise errors.OpExecError("Can't activate the instance's disks")
1870

    
1871
      result = self.rpc.call_instance_start(target_node.uuid,
1872
                                            (self.instance, None, None), False,
1873
                                            self.op.reason)
1874
      msg = result.fail_msg
1875
      if msg:
1876
        ShutdownInstanceDisks(self, self.instance)
1877
        raise errors.OpExecError("Could not start instance %s on node %s: %s" %
1878
                                 (self.instance.name, target_node.name, msg))
1879

    
1880

    
1881
class LUInstanceMultiAlloc(NoHooksLU):
1882
  """Allocates multiple instances at the same time.
1883

1884
  """
1885
  REQ_BGL = False
1886

    
1887
  def CheckArguments(self):
1888
    """Check arguments.
1889

1890
    """
1891
    nodes = []
1892
    for inst in self.op.instances:
1893
      if inst.iallocator is not None:
1894
        raise errors.OpPrereqError("iallocator are not allowed to be set on"
1895
                                   " instance objects", errors.ECODE_INVAL)
1896
      nodes.append(bool(inst.pnode))
1897
      if inst.disk_template in constants.DTS_INT_MIRROR:
1898
        nodes.append(bool(inst.snode))
1899

    
1900
    has_nodes = compat.any(nodes)
1901
    if compat.all(nodes) ^ has_nodes:
1902
      raise errors.OpPrereqError("There are instance objects providing"
1903
                                 " pnode/snode while others do not",
1904
                                 errors.ECODE_INVAL)
1905

    
1906
    if not has_nodes and self.op.iallocator is None:
1907
      default_iallocator = self.cfg.GetDefaultIAllocator()
1908
      if default_iallocator:
1909
        self.op.iallocator = default_iallocator
1910
      else:
1911
        raise errors.OpPrereqError("No iallocator or nodes on the instances"
1912
                                   " given and no cluster-wide default"
1913
                                   " iallocator found; please specify either"
1914
                                   " an iallocator or nodes on the instances"
1915
                                   " or set a cluster-wide default iallocator",
1916
                                   errors.ECODE_INVAL)
1917

    
1918
    _CheckOpportunisticLocking(self.op)
1919

    
1920
    dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
1921
    if dups:
1922
      raise errors.OpPrereqError("There are duplicate instance names: %s" %
1923
                                 utils.CommaJoin(dups), errors.ECODE_INVAL)
1924

    
1925
  def ExpandNames(self):
1926
    """Calculate the locks.
1927

1928
    """
1929
    self.share_locks = ShareAll()
1930
    self.needed_locks = {
1931
      # iallocator will select nodes and even if no iallocator is used,
1932
      # collisions with LUInstanceCreate should be avoided
1933
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
1934
      }
1935

    
1936
    if self.op.iallocator:
1937
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
1938
      self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
1939

    
1940
      if self.op.opportunistic_locking:
1941
        self.opportunistic_locks[locking.LEVEL_NODE] = True
1942
        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
1943
    else:
1944
      nodeslist = []
1945
      for inst in self.op.instances:
1946
        (inst.pnode_uuid, inst.pnode) = \
1947
          ExpandNodeUuidAndName(self.cfg, inst.pnode_uuid, inst.pnode)
1948
        nodeslist.append(inst.pnode_uuid)
1949
        if inst.snode is not None:
1950
          (inst.snode_uuid, inst.snode) = \
1951
            ExpandNodeUuidAndName(self.cfg, inst.snode_uuid, inst.snode)
1952
          nodeslist.append(inst.snode_uuid)
1953

    
1954
      self.needed_locks[locking.LEVEL_NODE] = nodeslist
1955
      # Lock resources of instance's primary and secondary nodes (copy to
1956
      # prevent accidential modification)
1957
      self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
1958

    
1959
  def CheckPrereq(self):
1960
    """Check prerequisite.
1961

1962
    """
1963
    if self.op.iallocator:
1964
      cluster = self.cfg.GetClusterInfo()
1965
      default_vg = self.cfg.GetVGName()
1966
      ec_id = self.proc.GetECId()
1967

    
1968
      if self.op.opportunistic_locking:
1969
        # Only consider nodes for which a lock is held
1970
        node_whitelist = self.cfg.GetNodeNames(
1971
                           list(self.owned_locks(locking.LEVEL_NODE)))
1972
      else:
1973
        node_whitelist = None
1974

    
1975
      insts = [_CreateInstanceAllocRequest(op, ComputeDisks(op, default_vg),
1976
                                           _ComputeNics(op, cluster, None,
1977
                                                        self.cfg, ec_id),
1978
                                           _ComputeFullBeParams(op, cluster),
1979
                                           node_whitelist)
1980
               for op in self.op.instances]
1981

    
1982
      req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
1983
      ial = iallocator.IAllocator(self.cfg, self.rpc, req)
1984

    
1985
      ial.Run(self.op.iallocator)
1986

    
1987
      if not ial.success:
1988
        raise errors.OpPrereqError("Can't compute nodes using"
1989
                                   " iallocator '%s': %s" %
1990
                                   (self.op.iallocator, ial.info),
1991
                                   errors.ECODE_NORES)
1992

    
1993
      self.ia_result = ial.result
1994

    
1995
    if self.op.dry_run:
1996
      self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
1997
        constants.JOB_IDS_KEY: [],
1998
        })
1999

    
2000
  def _ConstructPartialResult(self):
2001
    """Contructs the partial result.
2002

2003
    """
2004
    if self.op.iallocator:
2005
      (allocatable, failed_insts) = self.ia_result
2006
      allocatable_insts = map(compat.fst, allocatable)
2007
    else:
2008
      allocatable_insts = [op.instance_name for op in self.op.instances]
2009
      failed_insts = []
2010

    
2011
    return {
2012
      constants.ALLOCATABLE_KEY: allocatable_insts,
2013
      constants.FAILED_KEY: failed_insts,
2014
      }
2015

    
2016
  def Exec(self, feedback_fn):
2017
    """Executes the opcode.
2018

2019
    """
2020
    jobs = []
2021
    if self.op.iallocator:
2022
      op2inst = dict((op.instance_name, op) for op in self.op.instances)
2023
      (allocatable, failed) = self.ia_result
2024

    
2025
      for (name, node_names) in allocatable:
2026
        op = op2inst.pop(name)
2027

    
2028
        (op.pnode_uuid, op.pnode) = \
2029
          ExpandNodeUuidAndName(self.cfg, None, node_names[0])
2030
        if len(node_names) > 1:
2031
          (op.snode_uuid, op.snode) = \
2032
            ExpandNodeUuidAndName(self.cfg, None, node_names[1])
2033

    
2034
          jobs.append([op])
2035

    
2036
        missing = set(op2inst.keys()) - set(failed)
2037
        assert not missing, \
2038
          "Iallocator did return incomplete result: %s" % \
2039
          utils.CommaJoin(missing)
2040
    else:
2041
      jobs.extend([op] for op in self.op.instances)
2042

    
2043
    return ResultWithJobs(jobs, **self._ConstructPartialResult())
2044

    
2045

    
2046
class _InstNicModPrivate:
2047
  """Data structure for network interface modifications.
2048

2049
  Used by L{LUInstanceSetParams}.
2050

2051
  """
2052
  def __init__(self):
2053
    self.params = None
2054
    self.filled = None
2055

    
2056

    
2057
def _PrepareContainerMods(mods, private_fn):
2058
  """Prepares a list of container modifications by adding a private data field.
2059

2060
  @type mods: list of tuples; (operation, index, parameters)
2061
  @param mods: List of modifications
2062
  @type private_fn: callable or None
2063
  @param private_fn: Callable for constructing a private data field for a
2064
    modification
2065
  @rtype: list
2066

2067
  """
2068
  if private_fn is None:
2069
    fn = lambda: None
2070
  else:
2071
    fn = private_fn
2072

    
2073
  return [(op, idx, params, fn()) for (op, idx, params) in mods]
2074

    
2075

    
2076
def _CheckNodesPhysicalCPUs(lu, node_uuids, requested, hypervisor_specs):
2077
  """Checks if nodes have enough physical CPUs
2078

2079
  This function checks if all given nodes have the needed number of
2080
  physical CPUs. In case any node has less CPUs or we cannot get the
2081
  information from the node, this function raises an OpPrereqError
2082
  exception.
2083

2084
  @type lu: C{LogicalUnit}
2085
  @param lu: a logical unit from which we get configuration data
2086
  @type node_uuids: C{list}
2087
  @param node_uuids: the list of node UUIDs to check
2088
  @type requested: C{int}
2089
  @param requested: the minimum acceptable number of physical CPUs
2090
  @type hypervisor_specs: list of pairs (string, dict of strings)
2091
  @param hypervisor_specs: list of hypervisor specifications in
2092
      pairs (hypervisor_name, hvparams)
2093
  @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
2094
      or we cannot check the node
2095

2096
  """
2097
  nodeinfo = lu.rpc.call_node_info(node_uuids, None, hypervisor_specs)
2098
  for node_uuid in node_uuids:
2099
    info = nodeinfo[node_uuid]
2100
    node_name = lu.cfg.GetNodeName(node_uuid)
2101
    info.Raise("Cannot get current information from node %s" % node_name,
2102
               prereq=True, ecode=errors.ECODE_ENVIRON)
2103
    (_, _, (hv_info, )) = info.payload
2104
    num_cpus = hv_info.get("cpu_total", None)
2105
    if not isinstance(num_cpus, int):
2106
      raise errors.OpPrereqError("Can't compute the number of physical CPUs"
2107
                                 " on node %s, result was '%s'" %
2108
                                 (node_name, num_cpus), errors.ECODE_ENVIRON)
2109
    if requested > num_cpus:
2110
      raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
2111
                                 "required" % (node_name, num_cpus, requested),
2112
                                 errors.ECODE_NORES)
2113

    
2114

    
2115
def GetItemFromContainer(identifier, kind, container):
2116
  """Return the item refered by the identifier.
2117

2118
  @type identifier: string
2119
  @param identifier: Item index or name or UUID
2120
  @type kind: string
2121
  @param kind: One-word item description
2122
  @type container: list
2123
  @param container: Container to get the item from
2124

2125
  """
2126
  # Index
2127
  try:
2128
    idx = int(identifier)
2129
    if idx == -1:
2130
      # Append
2131
      absidx = len(container) - 1
2132
    elif idx < 0:
2133
      raise IndexError("Not accepting negative indices other than -1")
2134
    elif idx > len(container):
2135
      raise IndexError("Got %s index %s, but there are only %s" %
2136
                       (kind, idx, len(container)))
2137
    else:
2138
      absidx = idx
2139
    return (absidx, container[idx])
2140
  except ValueError:
2141
    pass
2142

    
2143
  for idx, item in enumerate(container):
2144
    if item.uuid == identifier or item.name == identifier:
2145
      return (idx, item)
2146

    
2147
  raise errors.OpPrereqError("Cannot find %s with identifier %s" %
2148
                             (kind, identifier), errors.ECODE_NOENT)
2149

    
2150

    
2151
def _ApplyContainerMods(kind, container, chgdesc, mods,
2152
                        create_fn, modify_fn, remove_fn,
2153
                        post_add_fn=None):
2154
  """Applies descriptions in C{mods} to C{container}.
2155

2156
  @type kind: string
2157
  @param kind: One-word item description
2158
  @type container: list
2159
  @param container: Container to modify
2160
  @type chgdesc: None or list
2161
  @param chgdesc: List of applied changes
2162
  @type mods: list
2163
  @param mods: Modifications as returned by L{_PrepareContainerMods}
2164
  @type create_fn: callable
2165
  @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
2166
    receives absolute item index, parameters and private data object as added
2167
    by L{_PrepareContainerMods}, returns tuple containing new item and changes
2168
    as list
2169
  @type modify_fn: callable
2170
  @param modify_fn: Callback for modifying an existing item
2171
    (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
2172
    and private data object as added by L{_PrepareContainerMods}, returns
2173
    changes as list
2174
  @type remove_fn: callable
2175
  @param remove_fn: Callback on removing item; receives absolute item index,
2176
    item and private data object as added by L{_PrepareContainerMods}
2177
  @type post_add_fn: callable
2178
  @param post_add_fn: Callable for post-processing a newly created item after
2179
    it has been put into the container. It receives the index of the new item
2180
    and the new item as parameters.
2181

2182
  """
2183
  for (op, identifier, params, private) in mods:
2184
    changes = None
2185

    
2186
    if op == constants.DDM_ADD:
2187
      # Calculate where item will be added
2188
      # When adding an item, identifier can only be an index
2189
      try:
2190
        idx = int(identifier)
2191
      except ValueError:
2192
        raise errors.OpPrereqError("Only possitive integer or -1 is accepted as"
2193
                                   " identifier for %s" % constants.DDM_ADD,
2194
                                   errors.ECODE_INVAL)
2195
      if idx == -1:
2196
        addidx = len(container)
2197
      else:
2198
        if idx < 0:
2199
          raise IndexError("Not accepting negative indices other than -1")
2200
        elif idx > len(container):
2201
          raise IndexError("Got %s index %s, but there are only %s" %
2202
                           (kind, idx, len(container)))
2203
        addidx = idx
2204

    
2205
      if create_fn is None:
2206
        item = params
2207
      else:
2208
        (item, changes) = create_fn(addidx, params, private)
2209

    
2210
      if idx == -1:
2211
        container.append(item)
2212
      else:
2213
        assert idx >= 0
2214
        assert idx <= len(container)
2215
        # list.insert does so before the specified index
2216
        container.insert(idx, item)
2217

    
2218
      if post_add_fn is not None:
2219
        post_add_fn(addidx, item)
2220

    
2221
    else:
2222
      # Retrieve existing item
2223
      (absidx, item) = GetItemFromContainer(identifier, kind, container)
2224

    
2225
      if op == constants.DDM_REMOVE:
2226
        assert not params
2227

    
2228
        changes = [("%s/%s" % (kind, absidx), "remove")]
2229

    
2230
        if remove_fn is not None:
2231
          msg = remove_fn(absidx, item, private)
2232
          if msg:
2233
            changes.append(("%s/%s" % (kind, absidx), msg))
2234

    
2235
        assert container[absidx] == item
2236
        del container[absidx]
2237
      elif op == constants.DDM_MODIFY:
2238
        if modify_fn is not None:
2239
          changes = modify_fn(absidx, item, params, private)
2240
      else:
2241
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2242

    
2243
    assert _TApplyContModsCbChanges(changes)
2244

    
2245
    if not (chgdesc is None or changes is None):
2246
      chgdesc.extend(changes)
2247

    
2248

    
2249
def _UpdateIvNames(base_index, disks):
2250
  """Updates the C{iv_name} attribute of disks.
2251

2252
  @type disks: list of L{objects.Disk}
2253

2254
  """
2255
  for (idx, disk) in enumerate(disks):
2256
    disk.iv_name = "disk/%s" % (base_index + idx, )
2257

    
2258

    
2259
class LUInstanceSetParams(LogicalUnit):
2260
  """Modifies an instances's parameters.
2261

2262
  """
2263
  HPATH = "instance-modify"
2264
  HTYPE = constants.HTYPE_INSTANCE
2265
  REQ_BGL = False
2266

    
2267
  @staticmethod
2268
  def _UpgradeDiskNicMods(kind, mods, verify_fn):
2269
    assert ht.TList(mods)
2270
    assert not mods or len(mods[0]) in (2, 3)
2271

    
2272
    if mods and len(mods[0]) == 2:
2273
      result = []
2274

    
2275
      addremove = 0
2276
      for op, params in mods:
2277
        if op in (constants.DDM_ADD, constants.DDM_REMOVE):
2278
          result.append((op, -1, params))
2279
          addremove += 1
2280

    
2281
          if addremove > 1:
2282
            raise errors.OpPrereqError("Only one %s add or remove operation is"
2283
                                       " supported at a time" % kind,
2284
                                       errors.ECODE_INVAL)
2285
        else:
2286
          result.append((constants.DDM_MODIFY, op, params))
2287

    
2288
      assert verify_fn(result)
2289
    else:
2290
      result = mods
2291

    
2292
    return result
2293

    
2294
  @staticmethod
2295
  def _CheckMods(kind, mods, key_types, item_fn):
2296
    """Ensures requested disk/NIC modifications are valid.
2297

2298
    """
2299
    for (op, _, params) in mods:
2300
      assert ht.TDict(params)
2301

    
2302
      # If 'key_types' is an empty dict, we assume we have an
2303
      # 'ext' template and thus do not ForceDictType
2304
      if key_types:
2305
        utils.ForceDictType(params, key_types)
2306

    
2307
      if op == constants.DDM_REMOVE:
2308
        if params:
2309
          raise errors.OpPrereqError("No settings should be passed when"
2310
                                     " removing a %s" % kind,
2311
                                     errors.ECODE_INVAL)
2312
      elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
2313
        item_fn(op, params)
2314
      else:
2315
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2316

    
2317
  @staticmethod
2318
  def _VerifyDiskModification(op, params, excl_stor):
2319
    """Verifies a disk modification.
2320

2321
    """
2322
    if op == constants.DDM_ADD:
2323
      mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
2324
      if mode not in constants.DISK_ACCESS_SET:
2325
        raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
2326
                                   errors.ECODE_INVAL)
2327

    
2328
      size = params.get(constants.IDISK_SIZE, None)
2329
      if size is None:
2330
        raise errors.OpPrereqError("Required disk parameter '%s' missing" %
2331
                                   constants.IDISK_SIZE, errors.ECODE_INVAL)
2332
      size = int(size)
2333

    
2334
      params[constants.IDISK_SIZE] = size
2335
      name = params.get(constants.IDISK_NAME, None)
2336
      if name is not None and name.lower() == constants.VALUE_NONE:
2337
        params[constants.IDISK_NAME] = None
2338

    
2339
      CheckSpindlesExclusiveStorage(params, excl_stor, True)
2340

    
2341
    elif op == constants.DDM_MODIFY:
2342
      if constants.IDISK_SIZE in params:
2343
        raise errors.OpPrereqError("Disk size change not possible, use"
2344
                                   " grow-disk", errors.ECODE_INVAL)
2345
      if len(params) > 2:
2346
        raise errors.OpPrereqError("Disk modification doesn't support"
2347
                                   " additional arbitrary parameters",
2348
                                   errors.ECODE_INVAL)
2349
      name = params.get(constants.IDISK_NAME, None)
2350
      if name is not None and name.lower() == constants.VALUE_NONE:
2351
        params[constants.IDISK_NAME] = None
2352

    
2353
  @staticmethod
2354
  def _VerifyNicModification(op, params):
2355
    """Verifies a network interface modification.
2356

2357
    """
2358
    if op in (constants.DDM_ADD, constants.DDM_MODIFY):
2359
      ip = params.get(constants.INIC_IP, None)
2360
      name = params.get(constants.INIC_NAME, None)
2361
      req_net = params.get(constants.INIC_NETWORK, None)
2362
      link = params.get(constants.NIC_LINK, None)
2363
      mode = params.get(constants.NIC_MODE, None)
2364
      if name is not None and name.lower() == constants.VALUE_NONE:
2365
        params[constants.INIC_NAME] = None
2366
      if req_net is not None:
2367
        if req_net.lower() == constants.VALUE_NONE:
2368
          params[constants.INIC_NETWORK] = None
2369
          req_net = None
2370
        elif link is not None or mode is not None:
2371
          raise errors.OpPrereqError("If network is given"
2372
                                     " mode or link should not",
2373
                                     errors.ECODE_INVAL)
2374

    
2375
      if op == constants.DDM_ADD:
2376
        macaddr = params.get(constants.INIC_MAC, None)
2377
        if macaddr is None:
2378
          params[constants.INIC_MAC] = constants.VALUE_AUTO
2379

    
2380
      if ip is not None:
2381
        if ip.lower() == constants.VALUE_NONE:
2382
          params[constants.INIC_IP] = None
2383
        else:
2384
          if ip.lower() == constants.NIC_IP_POOL:
2385
            if op == constants.DDM_ADD and req_net is None:
2386
              raise errors.OpPrereqError("If ip=pool, parameter network"
2387
                                         " cannot be none",
2388
                                         errors.ECODE_INVAL)
2389
          else:
2390
            if not netutils.IPAddress.IsValid(ip):
2391
              raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
2392
                                         errors.ECODE_INVAL)
2393

    
2394
      if constants.INIC_MAC in params:
2395
        macaddr = params[constants.INIC_MAC]
2396
        if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2397
          macaddr = utils.NormalizeAndValidateMac(macaddr)
2398

    
2399
        if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
2400
          raise errors.OpPrereqError("'auto' is not a valid MAC address when"
2401
                                     " modifying an existing NIC",
2402
                                     errors.ECODE_INVAL)
2403

    
2404
  def CheckArguments(self):
2405
    if not (self.op.nics or self.op.disks or self.op.disk_template or
2406
            self.op.hvparams or self.op.beparams or self.op.os_name or
2407
            self.op.osparams or self.op.offline is not None or
2408
            self.op.runtime_mem or self.op.pnode):
2409
      raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
2410

    
2411
    if self.op.hvparams:
2412
      CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
2413
                           "hypervisor", "instance", "cluster")
2414

    
2415
    self.op.disks = self._UpgradeDiskNicMods(
2416
      "disk", self.op.disks, ht.TSetParamsMods(ht.TIDiskParams))
2417
    self.op.nics = self._UpgradeDiskNicMods(
2418
      "NIC", self.op.nics, ht.TSetParamsMods(ht.TINicParams))
2419

    
2420
    if self.op.disks and self.op.disk_template is not None:
2421
      raise errors.OpPrereqError("Disk template conversion and other disk"
2422
                                 " changes not supported at the same time",
2423
                                 errors.ECODE_INVAL)
2424

    
2425
    if (self.op.disk_template and
2426
        self.op.disk_template in constants.DTS_INT_MIRROR and
2427
        self.op.remote_node is None):
2428
      raise errors.OpPrereqError("Changing the disk template to a mirrored"
2429
                                 " one requires specifying a secondary node",
2430
                                 errors.ECODE_INVAL)
2431

    
2432
    # Check NIC modifications
2433
    self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
2434
                    self._VerifyNicModification)
2435

    
2436
    if self.op.pnode:
2437
      (self.op.pnode_uuid, self.op.pnode) = \
2438
        ExpandNodeUuidAndName(self.cfg, self.op.pnode_uuid, self.op.pnode)
2439

    
2440
  def ExpandNames(self):
2441
    self._ExpandAndLockInstance()
2442
    self.needed_locks[locking.LEVEL_NODEGROUP] = []
2443
    # Can't even acquire node locks in shared mode as upcoming changes in
2444
    # Ganeti 2.6 will start to modify the node object on disk conversion
2445
    self.needed_locks[locking.LEVEL_NODE] = []
2446
    self.needed_locks[locking.LEVEL_NODE_RES] = []
2447
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2448
    # Look node group to look up the ipolicy
2449
    self.share_locks[locking.LEVEL_NODEGROUP] = 1
2450

    
2451
  def DeclareLocks(self, level):
2452
    if level == locking.LEVEL_NODEGROUP:
2453
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
2454
      # Acquire locks for the instance's nodegroups optimistically. Needs
2455
      # to be verified in CheckPrereq
2456
      self.needed_locks[locking.LEVEL_NODEGROUP] = \
2457
        self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
2458
    elif level == locking.LEVEL_NODE:
2459
      self._LockInstancesNodes()
2460
      if self.op.disk_template and self.op.remote_node:
2461
        (self.op.remote_node_uuid, self.op.remote_node) = \
2462
          ExpandNodeUuidAndName(self.cfg, self.op.remote_node_uuid,
2463
                                self.op.remote_node)
2464
        self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node_uuid)
2465
    elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
2466
      # Copy node locks
2467
      self.needed_locks[locking.LEVEL_NODE_RES] = \
2468
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
2469

    
2470
  def BuildHooksEnv(self):
2471
    """Build hooks env.
2472

2473
    This runs on the master, primary and secondaries.
2474

2475
    """
2476
    args = {}
2477
    if constants.BE_MINMEM in self.be_new:
2478
      args["minmem"] = self.be_new[constants.BE_MINMEM]
2479
    if constants.BE_MAXMEM in self.be_new:
2480
      args["maxmem"] = self.be_new[constants.BE_MAXMEM]
2481
    if constants.BE_VCPUS in self.be_new:
2482
      args["vcpus"] = self.be_new[constants.BE_VCPUS]
2483
    # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
2484
    # information at all.
2485

    
2486
    if self._new_nics is not None:
2487
      nics = []
2488

    
2489
      for nic in self._new_nics:
2490
        n = copy.deepcopy(nic)
2491
        nicparams = self.cluster.SimpleFillNIC(n.nicparams)
2492
        n.nicparams = nicparams
2493
        nics.append(NICToTuple(self, n))
2494

    
2495
      args["nics"] = nics
2496

    
2497
    env = BuildInstanceHookEnvByObject(self, self.instance, override=args)
2498
    if self.op.disk_template:
2499
      env["NEW_DISK_TEMPLATE"] = self.op.disk_template
2500
    if self.op.runtime_mem:
2501
      env["RUNTIME_MEMORY"] = self.op.runtime_mem
2502

    
2503
    return env
2504

    
2505
  def BuildHooksNodes(self):
2506
    """Build hooks nodes.
2507

2508
    """
2509
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
2510
    return (nl, nl)
2511

    
2512
  def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
2513
                              old_params, cluster, pnode_uuid):
2514

    
2515
    update_params_dict = dict([(key, params[key])
2516
                               for key in constants.NICS_PARAMETERS
2517
                               if key in params])
2518

    
2519
    req_link = update_params_dict.get(constants.NIC_LINK, None)
2520
    req_mode = update_params_dict.get(constants.NIC_MODE, None)
2521

    
2522
    new_net_uuid = None
2523
    new_net_uuid_or_name = params.get(constants.INIC_NETWORK, old_net_uuid)
2524
    if new_net_uuid_or_name:
2525
      new_net_uuid = self.cfg.LookupNetwork(new_net_uuid_or_name)
2526
      new_net_obj = self.cfg.GetNetwork(new_net_uuid)
2527

    
2528
    if old_net_uuid:
2529
      old_net_obj = self.cfg.GetNetwork(old_net_uuid)
2530

    
2531
    if new_net_uuid:
2532
      netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode_uuid)
2533
      if not netparams:
2534
        raise errors.OpPrereqError("No netparams found for the network"
2535
                                   " %s, probably not connected" %
2536
                                   new_net_obj.name, errors.ECODE_INVAL)
2537
      new_params = dict(netparams)
2538
    else:
2539
      new_params = GetUpdatedParams(old_params, update_params_dict)
2540

    
2541
    utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
2542

    
2543
    new_filled_params = cluster.SimpleFillNIC(new_params)
2544
    objects.NIC.CheckParameterSyntax(new_filled_params)
2545

    
2546
    new_mode = new_filled_params[constants.NIC_MODE]
2547
    if new_mode == constants.NIC_MODE_BRIDGED:
2548
      bridge = new_filled_params[constants.NIC_LINK]
2549
      msg = self.rpc.call_bridges_exist(pnode_uuid, [bridge]).fail_msg
2550
      if msg:
2551
        msg = "Error checking bridges on node '%s': %s" % \
2552
                (self.cfg.GetNodeName(pnode_uuid), msg)
2553
        if self.op.force:
2554
          self.warn.append(msg)
2555
        else:
2556
          raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
2557

    
2558
    elif new_mode == constants.NIC_MODE_ROUTED:
2559
      ip = params.get(constants.INIC_IP, old_ip)
2560
      if ip is None:
2561
        raise errors.OpPrereqError("Cannot set the NIC IP address to None"
2562
                                   " on a routed NIC", errors.ECODE_INVAL)
2563

    
2564
    elif new_mode == constants.NIC_MODE_OVS:
2565
      # TODO: check OVS link
2566
      self.LogInfo("OVS links are currently not checked for correctness")
2567

    
2568
    if constants.INIC_MAC in params:
2569
      mac = params[constants.INIC_MAC]
2570
      if mac is None:
2571
        raise errors.OpPrereqError("Cannot unset the NIC MAC address",
2572
                                   errors.ECODE_INVAL)
2573
      elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2574
        # otherwise generate the MAC address
2575
        params[constants.INIC_MAC] = \
2576
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2577
      else:
2578
        # or validate/reserve the current one
2579
        try:
2580
          self.cfg.ReserveMAC(mac, self.proc.GetECId())
2581
        except errors.ReservationError:
2582
          raise errors.OpPrereqError("MAC address '%s' already in use"
2583
                                     " in cluster" % mac,
2584
                                     errors.ECODE_NOTUNIQUE)
2585
    elif new_net_uuid != old_net_uuid:
2586

    
2587
      def get_net_prefix(net_uuid):
2588
        mac_prefix = None
2589
        if net_uuid:
2590
          nobj = self.cfg.GetNetwork(net_uuid)
2591
          mac_prefix = nobj.mac_prefix
2592

    
2593
        return mac_prefix
2594

    
2595
      new_prefix = get_net_prefix(new_net_uuid)
2596
      old_prefix = get_net_prefix(old_net_uuid)
2597
      if old_prefix != new_prefix:
2598
        params[constants.INIC_MAC] = \
2599
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2600

    
2601
    # if there is a change in (ip, network) tuple
2602
    new_ip = params.get(constants.INIC_IP, old_ip)
2603
    if (new_ip, new_net_uuid) != (old_ip, old_net_uuid):
2604
      if new_ip:
2605
        # if IP is pool then require a network and generate one IP
2606
        if new_ip.lower() == constants.NIC_IP_POOL:
2607
          if new_net_uuid:
2608
            try:
2609
              new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId())
2610
            except errors.ReservationError:
2611
              raise errors.OpPrereqError("Unable to get a free IP"
2612
                                         " from the address pool",
2613
                                         errors.ECODE_STATE)
2614
            self.LogInfo("Chose IP %s from network %s",
2615
                         new_ip,
2616
                         new_net_obj.name)
2617
            params[constants.INIC_IP] = new_ip
2618
          else:
2619
            raise errors.OpPrereqError("ip=pool, but no network found",
2620
                                       errors.ECODE_INVAL)
2621
        # Reserve new IP if in the new network if any
2622
        elif new_net_uuid:
2623
          try:
2624
            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId(),
2625
                               check=self.op.conflicts_check)
2626
            self.LogInfo("Reserving IP %s in network %s",
2627
                         new_ip, new_net_obj.name)
2628
          except errors.ReservationError:
2629
            raise errors.OpPrereqError("IP %s not available in network %s" %
2630
                                       (new_ip, new_net_obj.name),
2631
                                       errors.ECODE_NOTUNIQUE)
2632
        # new network is None so check if new IP is a conflicting IP
2633
        elif self.op.conflicts_check:
2634
          _CheckForConflictingIp(self, new_ip, pnode_uuid)
2635

    
2636
      # release old IP if old network is not None
2637
      if old_ip and old_net_uuid:
2638
        try:
2639
          self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId())
2640
        except errors.AddressPoolError:
2641
          logging.warning("Release IP %s not contained in network %s",
2642
                          old_ip, old_net_obj.name)
2643

    
2644
    # there are no changes in (ip, network) tuple and old network is not None
2645
    elif (old_net_uuid is not None and
2646
          (req_link is not None or req_mode is not None)):
2647
      raise errors.OpPrereqError("Not allowed to change link or mode of"
2648
                                 " a NIC that is connected to a network",
2649
                                 errors.ECODE_INVAL)
2650

    
2651
    private.params = new_params
2652
    private.filled = new_filled_params
2653

    
2654
  def _PreCheckDiskTemplate(self, pnode_info):
2655
    """CheckPrereq checks related to a new disk template."""
2656
    # Arguments are passed to avoid configuration lookups
2657
    pnode_uuid = self.instance.primary_node
2658
    if self.instance.disk_template == self.op.disk_template:
2659
      raise errors.OpPrereqError("Instance already has disk template %s" %
2660
                                 self.instance.disk_template,
2661
                                 errors.ECODE_INVAL)
2662

    
2663
    if not self.cluster.IsDiskTemplateEnabled(self.op.disk_template):
2664
      raise errors.OpPrereqError("Disk template '%s' is not enabled for this"
2665
                                 " cluster." % self.op.disk_template)
2666

    
2667
    if (self.instance.disk_template,
2668
        self.op.disk_template) not in self._DISK_CONVERSIONS:
2669
      raise errors.OpPrereqError("Unsupported disk template conversion from"
2670
                                 " %s to %s" % (self.instance.disk_template,
2671
                                                self.op.disk_template),
2672
                                 errors.ECODE_INVAL)
2673
    CheckInstanceState(self, self.instance, INSTANCE_DOWN,
2674
                       msg="cannot change disk template")
2675
    if self.op.disk_template in constants.DTS_INT_MIRROR:
2676
      if self.op.remote_node_uuid == pnode_uuid:
2677
        raise errors.OpPrereqError("Given new secondary node %s is the same"
2678
                                   " as the primary node of the instance" %
2679
                                   self.op.remote_node, errors.ECODE_STATE)
2680
      CheckNodeOnline(self, self.op.remote_node_uuid)
2681
      CheckNodeNotDrained(self, self.op.remote_node_uuid)
2682
      # FIXME: here we assume that the old instance type is DT_PLAIN
2683
      assert self.instance.disk_template == constants.DT_PLAIN
2684
      disks = [{constants.IDISK_SIZE: d.size,
2685
                constants.IDISK_VG: d.logical_id[0]}
2686
               for d in self.instance.disks]
2687
      required = ComputeDiskSizePerVG(self.op.disk_template, disks)
2688
      CheckNodesFreeDiskPerVG(self, [self.op.remote_node_uuid], required)
2689

    
2690
      snode_info = self.cfg.GetNodeInfo(self.op.remote_node_uuid)
2691
      snode_group = self.cfg.GetNodeGroup(snode_info.group)
2692
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(self.cluster,
2693
                                                              snode_group)
2694
      CheckTargetNodeIPolicy(self, ipolicy, self.instance, snode_info, self.cfg,
2695
                             ignore=self.op.ignore_ipolicy)
2696
      if pnode_info.group != snode_info.group:
2697
        self.LogWarning("The primary and secondary nodes are in two"
2698
                        " different node groups; the disk parameters"
2699
                        " from the first disk's node group will be"
2700
                        " used")
2701

    
2702
    if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
2703
      # Make sure none of the nodes require exclusive storage
2704
      nodes = [pnode_info]
2705
      if self.op.disk_template in constants.DTS_INT_MIRROR:
2706
        assert snode_info
2707
        nodes.append(snode_info)
2708
      has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
2709
      if compat.any(map(has_es, nodes)):
2710
        errmsg = ("Cannot convert disk template from %s to %s when exclusive"
2711
                  " storage is enabled" % (self.instance.disk_template,
2712
                                           self.op.disk_template))
2713
        raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
2714

    
2715
  def _PreCheckDisks(self, ispec):
2716
    """CheckPrereq checks related to disk changes.
2717

2718
    @type ispec: dict
2719
    @param ispec: instance specs to be updated with the new disks
2720

2721
    """
2722
    self.diskparams = self.cfg.GetInstanceDiskParams(self.instance)
2723

    
2724
    excl_stor = compat.any(
2725
      rpc.GetExclusiveStorageForNodes(self.cfg,
2726
                                      self.instance.all_nodes).values()
2727
      )
2728

    
2729
    # Check disk modifications. This is done here and not in CheckArguments
2730
    # (as with NICs), because we need to know the instance's disk template
2731
    ver_fn = lambda op, par: self._VerifyDiskModification(op, par, excl_stor)
2732
    if self.instance.disk_template == constants.DT_EXT:
2733
      self._CheckMods("disk", self.op.disks, {}, ver_fn)
2734
    else:
2735
      self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
2736
                      ver_fn)
2737

    
2738
    self.diskmod = _PrepareContainerMods(self.op.disks, None)
2739

    
2740
    # Check the validity of the `provider' parameter
2741
    if self.instance.disk_template in constants.DT_EXT:
2742
      for mod in self.diskmod:
2743
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2744
        if mod[0] == constants.DDM_ADD:
2745
          if ext_provider is None:
2746
            raise errors.OpPrereqError("Instance template is '%s' and parameter"
2747
                                       " '%s' missing, during disk add" %
2748
                                       (constants.DT_EXT,
2749
                                        constants.IDISK_PROVIDER),
2750
                                       errors.ECODE_NOENT)
2751
        elif mod[0] == constants.DDM_MODIFY:
2752
          if ext_provider:
2753
            raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
2754
                                       " modification" %
2755
                                       constants.IDISK_PROVIDER,
2756
                                       errors.ECODE_INVAL)
2757
    else:
2758
      for mod in self.diskmod:
2759
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2760
        if ext_provider is not None:
2761
          raise errors.OpPrereqError("Parameter '%s' is only valid for"
2762
                                     " instances of type '%s'" %
2763
                                     (constants.IDISK_PROVIDER,
2764
                                      constants.DT_EXT),
2765
                                     errors.ECODE_INVAL)
2766

    
2767
    if not self.op.wait_for_sync and self.instance.disks_active:
2768
      for mod in self.diskmod:
2769
        if mod[0] == constants.DDM_ADD:
2770
          raise errors.OpPrereqError("Can't add a disk to an instance with"
2771
                                     " activated disks and"
2772
                                     " --no-wait-for-sync given.",
2773
                                     errors.ECODE_INVAL)
2774

    
2775
    if self.op.disks and self.instance.disk_template == constants.DT_DISKLESS:
2776
      raise errors.OpPrereqError("Disk operations not supported for"
2777
                                 " diskless instances", errors.ECODE_INVAL)
2778

    
2779
    def _PrepareDiskMod(_, disk, params, __):
2780
      disk.name = params.get(constants.IDISK_NAME, None)
2781

    
2782
    # Verify disk changes (operating on a copy)
2783
    disks = copy.deepcopy(self.instance.disks)
2784
    _ApplyContainerMods("disk", disks, None, self.diskmod, None,
2785
                        _PrepareDiskMod, None)
2786
    utils.ValidateDeviceNames("disk", disks)
2787
    if len(disks) > constants.MAX_DISKS:
2788
      raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
2789
                                 " more" % constants.MAX_DISKS,
2790
                                 errors.ECODE_STATE)
2791
    disk_sizes = [disk.size for disk in self.instance.disks]
2792
    disk_sizes.extend(params["size"] for (op, idx, params, private) in
2793
                      self.diskmod if op == constants.DDM_ADD)
2794
    ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
2795
    ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
2796

    
2797
    if self.op.offline is not None and self.op.offline:
2798
      CheckInstanceState(self, self.instance, CAN_CHANGE_INSTANCE_OFFLINE,
2799
                         msg="can't change to offline")
2800

    
2801
  def CheckPrereq(self):
2802
    """Check prerequisites.
2803

2804
    This only checks the instance list against the existing names.
2805

2806
    """
2807
    assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
2808
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
2809
    self.cluster = self.cfg.GetClusterInfo()
2810
    cluster_hvparams = self.cluster.hvparams[self.instance.hypervisor]
2811

    
2812
    assert self.instance is not None, \
2813
      "Cannot retrieve locked instance %s" % self.op.instance_name
2814

    
2815
    pnode_uuid = self.instance.primary_node
2816

    
2817
    self.warn = []
2818

    
2819
    if (self.op.pnode_uuid is not None and self.op.pnode_uuid != pnode_uuid and
2820
        not self.op.force):
2821
      # verify that the instance is not up
2822
      instance_info = self.rpc.call_instance_info(
2823
          pnode_uuid, self.instance.name, self.instance.hypervisor,
2824
          cluster_hvparams)
2825
      if instance_info.fail_msg:
2826
        self.warn.append("Can't get instance runtime information: %s" %
2827
                         instance_info.fail_msg)
2828
      elif instance_info.payload:
2829
        raise errors.OpPrereqError("Instance is still running on %s" %
2830
                                   self.cfg.GetNodeName(pnode_uuid),
2831
                                   errors.ECODE_STATE)
2832

    
2833
    assert pnode_uuid in self.owned_locks(locking.LEVEL_NODE)
2834
    node_uuids = list(self.instance.all_nodes)
2835
    pnode_info = self.cfg.GetNodeInfo(pnode_uuid)
2836

    
2837
    #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
2838
    assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
2839
    group_info = self.cfg.GetNodeGroup(pnode_info.group)
2840

    
2841
    # dictionary with instance information after the modification
2842
    ispec = {}
2843

    
2844
    if self.op.hotplug or self.op.hotplug_if_possible:
2845
      result = self.rpc.call_hotplug_supported(self.instance.primary_node,
2846
                                               self.instance)
2847
      if result.fail_msg:
2848
        if self.op.hotplug:
2849
          result.Raise("Hotplug is not possible: %s" % result.fail_msg,
2850
                       prereq=True)
2851
        else:
2852
          self.LogWarning(result.fail_msg)
2853
          self.op.hotplug = False
2854
          self.LogInfo("Modification will take place without hotplugging.")
2855
      else:
2856
        self.op.hotplug = True
2857

    
2858
    # Prepare NIC modifications
2859
    self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
2860

    
2861
    # OS change
2862
    if self.op.os_name and not self.op.force:
2863
      CheckNodeHasOS(self, self.instance.primary_node, self.op.os_name,
2864
                     self.op.force_variant)
2865
      instance_os = self.op.os_name
2866
    else:
2867
      instance_os = self.instance.os
2868

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

    
2872
    if self.op.disk_template:
2873
      self._PreCheckDiskTemplate(pnode_info)
2874

    
2875
    self._PreCheckDisks(ispec)
2876

    
2877
    # hvparams processing
2878
    if self.op.hvparams:
2879
      hv_type = self.instance.hypervisor
2880
      i_hvdict = GetUpdatedParams(self.instance.hvparams, self.op.hvparams)
2881
      utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
2882
      hv_new = self.cluster.SimpleFillHV(hv_type, self.instance.os, i_hvdict)
2883

    
2884
      # local check
2885
      hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
2886
      CheckHVParams(self, node_uuids, self.instance.hypervisor, hv_new)
2887
      self.hv_proposed = self.hv_new = hv_new # the new actual values
2888
      self.hv_inst = i_hvdict # the new dict (without defaults)
2889
    else:
2890
      self.hv_proposed = self.cluster.SimpleFillHV(self.instance.hypervisor,
2891
                                                   self.instance.os,
2892
                                                   self.instance.hvparams)
2893
      self.hv_new = self.hv_inst = {}
2894

    
2895
    # beparams processing
2896
    if self.op.beparams:
2897
      i_bedict = GetUpdatedParams(self.instance.beparams, self.op.beparams,
2898
                                  use_none=True)
2899
      objects.UpgradeBeParams(i_bedict)
2900
      utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
2901
      be_new = self.cluster.SimpleFillBE(i_bedict)
2902
      self.be_proposed = self.be_new = be_new # the new actual values
2903
      self.be_inst = i_bedict # the new dict (without defaults)
2904
    else:
2905
      self.be_new = self.be_inst = {}
2906
      self.be_proposed = self.cluster.SimpleFillBE(self.instance.beparams)
2907
    be_old = self.cluster.FillBE(self.instance)
2908

    
2909
    # CPU param validation -- checking every time a parameter is
2910
    # changed to cover all cases where either CPU mask or vcpus have
2911
    # changed
2912
    if (constants.BE_VCPUS in self.be_proposed and
2913
        constants.HV_CPU_MASK in self.hv_proposed):
2914
      cpu_list = \
2915
        utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
2916
      # Verify mask is consistent with number of vCPUs. Can skip this
2917
      # test if only 1 entry in the CPU mask, which means same mask
2918
      # is applied to all vCPUs.
2919
      if (len(cpu_list) > 1 and
2920
          len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
2921
        raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
2922
                                   " CPU mask [%s]" %
2923
                                   (self.be_proposed[constants.BE_VCPUS],
2924
                                    self.hv_proposed[constants.HV_CPU_MASK]),
2925
                                   errors.ECODE_INVAL)
2926

    
2927
      # Only perform this test if a new CPU mask is given
2928
      if constants.HV_CPU_MASK in self.hv_new:
2929
        # Calculate the largest CPU number requested
2930
        max_requested_cpu = max(map(max, cpu_list))
2931
        # Check that all of the instance's nodes have enough physical CPUs to
2932
        # satisfy the requested CPU mask
2933
        hvspecs = [(self.instance.hypervisor,
2934
                    self.cfg.GetClusterInfo()
2935
                      .hvparams[self.instance.hypervisor])]
2936
        _CheckNodesPhysicalCPUs(self, self.instance.all_nodes,
2937
                                max_requested_cpu + 1,
2938
                                hvspecs)
2939

    
2940
    # osparams processing
2941
    if self.op.osparams:
2942
      i_osdict = GetUpdatedParams(self.instance.osparams, self.op.osparams)
2943
      CheckOSParams(self, True, node_uuids, instance_os, i_osdict)
2944
      self.os_inst = i_osdict # the new dict (without defaults)
2945
    else:
2946
      self.os_inst = {}
2947

    
2948
    #TODO(dynmem): do the appropriate check involving MINMEM
2949
    if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
2950
        be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
2951
      mem_check_list = [pnode_uuid]
2952
      if be_new[constants.BE_AUTO_BALANCE]:
2953
        # either we changed auto_balance to yes or it was from before
2954
        mem_check_list.extend(self.instance.secondary_nodes)
2955
      instance_info = self.rpc.call_instance_info(
2956
          pnode_uuid, self.instance.name, self.instance.hypervisor,
2957
          cluster_hvparams)
2958
      hvspecs = [(self.instance.hypervisor,
2959
                  cluster_hvparams)]
2960
      nodeinfo = self.rpc.call_node_info(mem_check_list, None,
2961
                                         hvspecs)
2962
      pninfo = nodeinfo[pnode_uuid]
2963
      msg = pninfo.fail_msg
2964
      if msg:
2965
        # Assume the primary node is unreachable and go ahead
2966
        self.warn.append("Can't get info from primary node %s: %s" %
2967
                         (self.cfg.GetNodeName(pnode_uuid), msg))
2968
      else:
2969
        (_, _, (pnhvinfo, )) = pninfo.payload
2970
        if not isinstance(pnhvinfo.get("memory_free", None), int):
2971
          self.warn.append("Node data from primary node %s doesn't contain"
2972
                           " free memory information" %
2973
                           self.cfg.GetNodeName(pnode_uuid))
2974
        elif instance_info.fail_msg:
2975
          self.warn.append("Can't get instance runtime information: %s" %
2976
                           instance_info.fail_msg)
2977
        else:
2978
          if instance_info.payload:
2979
            current_mem = int(instance_info.payload["memory"])
2980
          else:
2981
            # Assume instance not running
2982
            # (there is a slight race condition here, but it's not very
2983
            # probable, and we have no other way to check)
2984
            # TODO: Describe race condition
2985
            current_mem = 0
2986
          #TODO(dynmem): do the appropriate check involving MINMEM
2987
          miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
2988
                      pnhvinfo["memory_free"])
2989
          if miss_mem > 0:
2990
            raise errors.OpPrereqError("This change will prevent the instance"
2991
                                       " from starting, due to %d MB of memory"
2992
                                       " missing on its primary node" %
2993
                                       miss_mem, errors.ECODE_NORES)
2994

    
2995
      if be_new[constants.BE_AUTO_BALANCE]:
2996
        for node_uuid, nres in nodeinfo.items():
2997
          if node_uuid not in self.instance.secondary_nodes:
2998
            continue
2999
          nres.Raise("Can't get info from secondary node %s" %
3000
                     self.cfg.GetNodeName(node_uuid), prereq=True,
3001
                     ecode=errors.ECODE_STATE)
3002
          (_, _, (nhvinfo, )) = nres.payload
3003
          if not isinstance(nhvinfo.get("memory_free", None), int):
3004
            raise errors.OpPrereqError("Secondary node %s didn't return free"
3005
                                       " memory information" %
3006
                                       self.cfg.GetNodeName(node_uuid),
3007
                                       errors.ECODE_STATE)
3008
          #TODO(dynmem): do the appropriate check involving MINMEM
3009
          elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
3010
            raise errors.OpPrereqError("This change will prevent the instance"
3011
                                       " from failover to its secondary node"
3012
                                       " %s, due to not enough memory" %
3013
                                       self.cfg.GetNodeName(node_uuid),
3014
                                       errors.ECODE_STATE)
3015

    
3016
    if self.op.runtime_mem:
3017
      remote_info = self.rpc.call_instance_info(
3018
         self.instance.primary_node, self.instance.name,
3019
         self.instance.hypervisor,
3020
         cluster_hvparams)
3021
      remote_info.Raise("Error checking node %s" %
3022
                        self.cfg.GetNodeName(self.instance.primary_node))
3023
      if not remote_info.payload: # not running already
3024
        raise errors.OpPrereqError("Instance %s is not running" %
3025
                                   self.instance.name, errors.ECODE_STATE)
3026

    
3027
      current_memory = remote_info.payload["memory"]
3028
      if (not self.op.force and
3029
           (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
3030
            self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
3031
        raise errors.OpPrereqError("Instance %s must have memory between %d"
3032
                                   " and %d MB of memory unless --force is"
3033
                                   " given" %
3034
                                   (self.instance.name,
3035
                                    self.be_proposed[constants.BE_MINMEM],
3036
                                    self.be_proposed[constants.BE_MAXMEM]),
3037
                                   errors.ECODE_INVAL)
3038

    
3039
      delta = self.op.runtime_mem - current_memory
3040
      if delta > 0:
3041
        CheckNodeFreeMemory(
3042
            self, self.instance.primary_node,
3043
            "ballooning memory for instance %s" % self.instance.name, delta,
3044
            self.instance.hypervisor,
3045
            self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
3046

    
3047
    # make self.cluster visible in the functions below
3048
    cluster = self.cluster
3049

    
3050
    def _PrepareNicCreate(_, params, private):
3051
      self._PrepareNicModification(params, private, None, None,
3052
                                   {}, cluster, pnode_uuid)
3053
      return (None, None)
3054

    
3055
    def _PrepareNicMod(_, nic, params, private):
3056
      self._PrepareNicModification(params, private, nic.ip, nic.network,
3057
                                   nic.nicparams, cluster, pnode_uuid)
3058
      return None
3059

    
3060
    def _PrepareNicRemove(_, params, __):
3061
      ip = params.ip
3062
      net = params.network
3063
      if net is not None and ip is not None:
3064
        self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
3065

    
3066
    # Verify NIC changes (operating on copy)
3067
    nics = self.instance.nics[:]
3068
    _ApplyContainerMods("NIC", nics, None, self.nicmod,
3069
                        _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
3070
    if len(nics) > constants.MAX_NICS:
3071
      raise errors.OpPrereqError("Instance has too many network interfaces"
3072
                                 " (%d), cannot add more" % constants.MAX_NICS,
3073
                                 errors.ECODE_STATE)
3074

    
3075
    # Pre-compute NIC changes (necessary to use result in hooks)
3076
    self._nic_chgdesc = []
3077
    if self.nicmod:
3078
      # Operate on copies as this is still in prereq
3079
      nics = [nic.Copy() for nic in self.instance.nics]
3080
      _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
3081
                          self._CreateNewNic, self._ApplyNicMods,
3082
                          self._RemoveNic)
3083
      # Verify that NIC names are unique and valid
3084
      utils.ValidateDeviceNames("NIC", nics)
3085
      self._new_nics = nics
3086
      ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
3087
    else:
3088
      self._new_nics = None
3089
      ispec[constants.ISPEC_NIC_COUNT] = len(self.instance.nics)
3090

    
3091
    if not self.op.ignore_ipolicy:
3092
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(self.cluster,
3093
                                                              group_info)
3094

    
3095
      # Fill ispec with backend parameters
3096
      ispec[constants.ISPEC_SPINDLE_USE] = \
3097
        self.be_new.get(constants.BE_SPINDLE_USE, None)
3098
      ispec[constants.ISPEC_CPU_COUNT] = self.be_new.get(constants.BE_VCPUS,
3099
                                                         None)
3100

    
3101
      # Copy ispec to verify parameters with min/max values separately
3102
      if self.op.disk_template:
3103
        new_disk_template = self.op.disk_template
3104
      else:
3105
        new_disk_template = self.instance.disk_template
3106
      ispec_max = ispec.copy()
3107
      ispec_max[constants.ISPEC_MEM_SIZE] = \
3108
        self.be_new.get(constants.BE_MAXMEM, None)
3109
      res_max = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_max,
3110
                                                     new_disk_template)
3111
      ispec_min = ispec.copy()
3112
      ispec_min[constants.ISPEC_MEM_SIZE] = \
3113
        self.be_new.get(constants.BE_MINMEM, None)
3114
      res_min = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
3115
                                                     new_disk_template)
3116

    
3117
      if (res_max or res_min):
3118
        # FIXME: Improve error message by including information about whether
3119
        # the upper or lower limit of the parameter fails the ipolicy.
3120
        msg = ("Instance allocation to group %s (%s) violates policy: %s" %
3121
               (group_info, group_info.name,
3122
                utils.CommaJoin(set(res_max + res_min))))
3123
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
3124

    
3125
  def _ConvertPlainToDrbd(self, feedback_fn):
3126
    """Converts an instance from plain to drbd.
3127

3128
    """
3129
    feedback_fn("Converting template to drbd")
3130
    pnode_uuid = self.instance.primary_node
3131
    snode_uuid = self.op.remote_node_uuid
3132

    
3133
    assert self.instance.disk_template == constants.DT_PLAIN
3134

    
3135
    # create a fake disk info for _GenerateDiskTemplate
3136
    disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
3137
                  constants.IDISK_VG: d.logical_id[0],
3138
                  constants.IDISK_NAME: d.name}
3139
                 for d in self.instance.disks]
3140
    new_disks = GenerateDiskTemplate(self, self.op.disk_template,
3141
                                     self.instance.uuid, pnode_uuid,
3142
                                     [snode_uuid], disk_info, None, None, 0,
3143
                                     feedback_fn, self.diskparams)
3144
    anno_disks = rpc.AnnotateDiskParams(new_disks, self.diskparams)
3145
    p_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, pnode_uuid)
3146
    s_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, snode_uuid)
3147
    info = GetInstanceInfoText(self.instance)
3148
    feedback_fn("Creating additional volumes...")
3149
    # first, create the missing data and meta devices
3150
    for disk in anno_disks:
3151
      # unfortunately this is... not too nice
3152
      CreateSingleBlockDev(self, pnode_uuid, self.instance, disk.children[1],
3153
                           info, True, p_excl_stor)
3154
      for child in disk.children:
3155
        CreateSingleBlockDev(self, snode_uuid, self.instance, child, info, True,
3156
                             s_excl_stor)
3157
    # at this stage, all new LVs have been created, we can rename the
3158
    # old ones
3159
    feedback_fn("Renaming original volumes...")
3160
    rename_list = [(o, n.children[0].logical_id)
3161
                   for (o, n) in zip(self.instance.disks, new_disks)]
3162
    result = self.rpc.call_blockdev_rename(pnode_uuid, rename_list)
3163
    result.Raise("Failed to rename original LVs")
3164

    
3165
    feedback_fn("Initializing DRBD devices...")
3166
    # all child devices are in place, we can now create the DRBD devices
3167
    try:
3168
      for disk in anno_disks:
3169
        for (node_uuid, excl_stor) in [(pnode_uuid, p_excl_stor),
3170
                                       (snode_uuid, s_excl_stor)]:
3171
          f_create = node_uuid == pnode_uuid
3172
          CreateSingleBlockDev(self, node_uuid, self.instance, disk, info,
3173
                               f_create, excl_stor)
3174
    except errors.GenericError, e:
3175
      feedback_fn("Initializing of DRBD devices failed;"
3176
                  " renaming back original volumes...")
3177
      rename_back_list = [(n.children[0], o.logical_id)
3178
                          for (n, o) in zip(new_disks, self.instance.disks)]
3179
      result = self.rpc.call_blockdev_rename(pnode_uuid, rename_back_list)
3180
      result.Raise("Failed to rename LVs back after error %s" % str(e))
3181
      raise
3182

    
3183
    # at this point, the instance has been modified
3184
    self.instance.disk_template = constants.DT_DRBD8
3185
    self.instance.disks = new_disks
3186
    self.cfg.Update(self.instance, feedback_fn)
3187

    
3188
    # Release node locks while waiting for sync
3189
    ReleaseLocks(self, locking.LEVEL_NODE)
3190

    
3191
    # disks are created, waiting for sync
3192
    disk_abort = not WaitForSync(self, self.instance,
3193
                                 oneshot=not self.op.wait_for_sync)
3194
    if disk_abort:
3195
      raise errors.OpExecError("There are some degraded disks for"
3196
                               " this instance, please cleanup manually")
3197

    
3198
    # Node resource locks will be released by caller
3199

    
3200
  def _ConvertDrbdToPlain(self, feedback_fn):
3201
    """Converts an instance from drbd to plain.
3202

3203
    """
3204
    assert len(self.instance.secondary_nodes) == 1
3205
    assert self.instance.disk_template == constants.DT_DRBD8
3206

    
3207
    pnode_uuid = self.instance.primary_node
3208
    snode_uuid = self.instance.secondary_nodes[0]
3209
    feedback_fn("Converting template to plain")
3210

    
3211
    old_disks = AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
3212
    new_disks = [d.children[0] for d in self.instance.disks]
3213

    
3214
    # copy over size, mode and name
3215
    for parent, child in zip(old_disks, new_disks):
3216
      child.size = parent.size
3217
      child.mode = parent.mode
3218
      child.name = parent.name
3219

    
3220
    # this is a DRBD disk, return its port to the pool
3221
    # NOTE: this must be done right before the call to cfg.Update!
3222
    for disk in old_disks:
3223
      tcp_port = disk.logical_id[2]
3224
      self.cfg.AddTcpUdpPort(tcp_port)
3225

    
3226
    # update instance structure
3227
    self.instance.disks = new_disks
3228
    self.instance.disk_template = constants.DT_PLAIN
3229
    _UpdateIvNames(0, self.instance.disks)
3230
    self.cfg.Update(self.instance, feedback_fn)
3231

    
3232
    # Release locks in case removing disks takes a while
3233
    ReleaseLocks(self, locking.LEVEL_NODE)
3234

    
3235
    feedback_fn("Removing volumes on the secondary node...")
3236
    for disk in old_disks:
3237
      result = self.rpc.call_blockdev_remove(snode_uuid, (disk, self.instance))
3238
      result.Warn("Could not remove block device %s on node %s,"
3239
                  " continuing anyway" %
3240
                  (disk.iv_name, self.cfg.GetNodeName(snode_uuid)),
3241
                  self.LogWarning)
3242

    
3243
    feedback_fn("Removing unneeded volumes on the primary node...")
3244
    for idx, disk in enumerate(old_disks):
3245
      meta = disk.children[1]
3246
      result = self.rpc.call_blockdev_remove(pnode_uuid, (meta, self.instance))
3247
      result.Warn("Could not remove metadata for disk %d on node %s,"
3248
                  " continuing anyway" %
3249
                  (idx, self.cfg.GetNodeName(pnode_uuid)),
3250
                  self.LogWarning)
3251

    
3252
  def _HotplugDevice(self, action, dev_type, device, extra, seq):
3253
    self.LogInfo("Trying to hotplug device...")
3254
    msg = "hotplug:"
3255
    result = self.rpc.call_hotplug_device(self.instance.primary_node,
3256
                                          self.instance, action, dev_type,
3257
                                          (device, self.instance),
3258
                                          extra, seq)
3259
    if result.fail_msg:
3260
      self.LogWarning("Could not hotplug device: %s" % result.fail_msg)
3261
      self.LogInfo("Continuing execution..")
3262
      msg += "failed"
3263
    else:
3264
      self.LogInfo("Hotplug done.")
3265
      msg += "done"
3266
    return msg
3267

    
3268
  def _CreateNewDisk(self, idx, params, _):
3269
    """Creates a new disk.
3270

3271
    """
3272
    # add a new disk
3273
    if self.instance.disk_template in constants.DTS_FILEBASED:
3274
      (file_driver, file_path) = self.instance.disks[0].logical_id
3275
      file_path = os.path.dirname(file_path)
3276
    else:
3277
      file_driver = file_path = None
3278

    
3279
    disk = \
3280
      GenerateDiskTemplate(self, self.instance.disk_template,
3281
                           self.instance.uuid, self.instance.primary_node,
3282
                           self.instance.secondary_nodes, [params], file_path,
3283
                           file_driver, idx, self.Log, self.diskparams)[0]
3284

    
3285
    new_disks = CreateDisks(self, self.instance, disks=[disk])
3286

    
3287
    if self.cluster.prealloc_wipe_disks:
3288
      # Wipe new disk
3289
      WipeOrCleanupDisks(self, self.instance,
3290
                         disks=[(idx, disk, 0)],
3291
                         cleanup=new_disks)
3292

    
3293
    changes = [
3294
      ("disk/%d" % idx,
3295
       "add:size=%s,mode=%s" % (disk.size, disk.mode)),
3296
      ]
3297
    if self.op.hotplug:
3298
      result = self.rpc.call_blockdev_assemble(self.instance.primary_node,
3299
                                               (disk, self.instance),
3300
                                               self.instance.name, True, idx)
3301
      if result.fail_msg:
3302
        changes.append(("disk/%d" % idx, "assemble:failed"))
3303
        self.LogWarning("Can't assemble newly created disk %d: %s",
3304
                        idx, result.fail_msg)
3305
      else:
3306
        _, link_name = result.payload
3307
        msg = self._HotplugDevice(constants.HOTPLUG_ACTION_ADD,
3308
                                  constants.HOTPLUG_TARGET_DISK,
3309
                                  disk, link_name, idx)
3310
        changes.append(("disk/%d" % idx, msg))
3311

    
3312
    return (disk, changes)
3313

    
3314
  def _PostAddDisk(self, _, disk):
3315
    if not WaitForSync(self, self.instance, disks=[disk],
3316
                       oneshot=not self.op.wait_for_sync):
3317
      raise errors.OpExecError("Failed to sync disks of %s" %
3318
                               self.instance.name)
3319

    
3320
    # the disk is active at this point, so deactivate it if the instance disks
3321
    # are supposed to be inactive
3322
    if not self.instance.disks_active:
3323
      ShutdownInstanceDisks(self, self.instance, disks=[disk])
3324

    
3325
  @staticmethod
3326
  def _ModifyDisk(idx, disk, params, _):
3327
    """Modifies a disk.
3328

3329
    """
3330
    changes = []
3331
    mode = params.get(constants.IDISK_MODE, None)
3332
    if mode:
3333
      disk.mode = mode
3334
      changes.append(("disk.mode/%d" % idx, disk.mode))
3335

    
3336
    name = params.get(constants.IDISK_NAME, None)
3337
    disk.name = name
3338
    changes.append(("disk.name/%d" % idx, disk.name))
3339

    
3340
    return changes
3341

    
3342
  def _RemoveDisk(self, idx, root, _):
3343
    """Removes a disk.
3344

3345
    """
3346
    hotmsg = ""
3347
    if self.op.hotplug:
3348
      hotmsg = self._HotplugDevice(constants.HOTPLUG_ACTION_REMOVE,
3349
                                   constants.HOTPLUG_TARGET_DISK,
3350
                                   root, None, idx)
3351
      ShutdownInstanceDisks(self, self.instance, [root])
3352

    
3353
    (anno_disk,) = AnnotateDiskParams(self.instance, [root], self.cfg)
3354
    for node_uuid, disk in anno_disk.ComputeNodeTree(
3355
                             self.instance.primary_node):
3356
      msg = self.rpc.call_blockdev_remove(node_uuid, (disk, self.instance)) \
3357
              .fail_msg
3358
      if msg:
3359
        self.LogWarning("Could not remove disk/%d on node '%s': %s,"
3360
                        " continuing anyway", idx,
3361
                        self.cfg.GetNodeName(node_uuid), msg)
3362

    
3363
    # if this is a DRBD disk, return its port to the pool
3364
    if root.dev_type in constants.DTS_DRBD:
3365
      self.cfg.AddTcpUdpPort(root.logical_id[2])
3366

    
3367
    return hotmsg
3368

    
3369
  def _CreateNewNic(self, idx, params, private):
3370
    """Creates data structure for a new network interface.
3371

3372
    """
3373
    mac = params[constants.INIC_MAC]
3374
    ip = params.get(constants.INIC_IP, None)
3375
    net = params.get(constants.INIC_NETWORK, None)
3376
    name = params.get(constants.INIC_NAME, None)
3377
    net_uuid = self.cfg.LookupNetwork(net)
3378
    #TODO: not private.filled?? can a nic have no nicparams??
3379
    nicparams = private.filled
3380
    nobj = objects.NIC(mac=mac, ip=ip, network=net_uuid, name=name,
3381
                       nicparams=nicparams)
3382
    nobj.uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
3383

    
3384
    changes = [
3385
      ("nic.%d" % idx,
3386
       "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" %
3387
       (mac, ip, private.filled[constants.NIC_MODE],
3388
       private.filled[constants.NIC_LINK], net)),
3389
      ]
3390

    
3391
    if self.op.hotplug:
3392
      msg = self._HotplugDevice(constants.HOTPLUG_ACTION_ADD,
3393
                                constants.HOTPLUG_TARGET_NIC,
3394
                                nobj, None, idx)
3395
      changes.append(("nic.%d" % idx, msg))
3396

    
3397
    return (nobj, changes)
3398

    
3399
  def _ApplyNicMods(self, idx, nic, params, private):
3400
    """Modifies a network interface.
3401

3402
    """
3403
    changes = []
3404

    
3405
    for key in [constants.INIC_MAC, constants.INIC_IP, constants.INIC_NAME]:
3406
      if key in params:
3407
        changes.append(("nic.%s/%d" % (key, idx), params[key]))
3408
        setattr(nic, key, params[key])
3409

    
3410
    new_net = params.get(constants.INIC_NETWORK, nic.network)
3411
    new_net_uuid = self.cfg.LookupNetwork(new_net)
3412
    if new_net_uuid != nic.network:
3413
      changes.append(("nic.network/%d" % idx, new_net))
3414
      nic.network = new_net_uuid
3415

    
3416
    if private.filled:
3417
      nic.nicparams = private.filled
3418

    
3419
      for (key, val) in nic.nicparams.items():
3420
        changes.append(("nic.%s/%d" % (key, idx), val))
3421

    
3422
    if self.op.hotplug:
3423
      msg = self._HotplugDevice(constants.HOTPLUG_ACTION_MODIFY,
3424
                                constants.HOTPLUG_TARGET_NIC,
3425
                                nic, None, idx)
3426
      changes.append(("nic/%d" % idx, msg))
3427

    
3428
    return changes
3429

    
3430
  def _RemoveNic(self, idx, nic, _):
3431
    if self.op.hotplug:
3432
      return self._HotplugDevice(constants.HOTPLUG_ACTION_REMOVE,
3433
                                 constants.HOTPLUG_TARGET_NIC,
3434
                                 nic, None, idx)
3435

    
3436
  def Exec(self, feedback_fn):
3437
    """Modifies an instance.
3438

3439
    All parameters take effect only at the next restart of the instance.
3440

3441
    """
3442
    # Process here the warnings from CheckPrereq, as we don't have a
3443
    # feedback_fn there.
3444
    # TODO: Replace with self.LogWarning
3445
    for warn in self.warn:
3446
      feedback_fn("WARNING: %s" % warn)
3447

    
3448
    assert ((self.op.disk_template is None) ^
3449
            bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
3450
      "Not owning any node resource locks"
3451

    
3452
    result = []
3453

    
3454
    # New primary node
3455
    if self.op.pnode_uuid:
3456
      self.instance.primary_node = self.op.pnode_uuid
3457

    
3458
    # runtime memory
3459
    if self.op.runtime_mem:
3460
      rpcres = self.rpc.call_instance_balloon_memory(self.instance.primary_node,
3461
                                                     self.instance,
3462
                                                     self.op.runtime_mem)
3463
      rpcres.Raise("Cannot modify instance runtime memory")
3464
      result.append(("runtime_memory", self.op.runtime_mem))
3465

    
3466
    # Apply disk changes
3467
    _ApplyContainerMods("disk", self.instance.disks, result, self.diskmod,
3468
                        self._CreateNewDisk, self._ModifyDisk,
3469
                        self._RemoveDisk, post_add_fn=self._PostAddDisk)
3470
    _UpdateIvNames(0, self.instance.disks)
3471

    
3472
    if self.op.disk_template:
3473
      if __debug__:
3474
        check_nodes = set(self.instance.all_nodes)
3475
        if self.op.remote_node_uuid:
3476
          check_nodes.add(self.op.remote_node_uuid)
3477
        for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
3478
          owned = self.owned_locks(level)
3479
          assert not (check_nodes - owned), \
3480
            ("Not owning the correct locks, owning %r, expected at least %r" %
3481
             (owned, check_nodes))
3482

    
3483
      r_shut = ShutdownInstanceDisks(self, self.instance)
3484
      if not r_shut:
3485
        raise errors.OpExecError("Cannot shutdown instance disks, unable to"
3486
                                 " proceed with disk template conversion")
3487
      mode = (self.instance.disk_template, self.op.disk_template)
3488
      try:
3489
        self._DISK_CONVERSIONS[mode](self, feedback_fn)
3490
      except:
3491
        self.cfg.ReleaseDRBDMinors(self.instance.uuid)
3492
        raise
3493
      result.append(("disk_template", self.op.disk_template))
3494

    
3495
      assert self.instance.disk_template == self.op.disk_template, \
3496
        ("Expected disk template '%s', found '%s'" %
3497
         (self.op.disk_template, self.instance.disk_template))
3498

    
3499
    # Release node and resource locks if there are any (they might already have
3500
    # been released during disk conversion)
3501
    ReleaseLocks(self, locking.LEVEL_NODE)
3502
    ReleaseLocks(self, locking.LEVEL_NODE_RES)
3503

    
3504
    # Apply NIC changes
3505
    if self._new_nics is not None:
3506
      self.instance.nics = self._new_nics
3507
      result.extend(self._nic_chgdesc)
3508

    
3509
    # hvparams changes
3510
    if self.op.hvparams:
3511
      self.instance.hvparams = self.hv_inst
3512
      for key, val in self.op.hvparams.iteritems():
3513
        result.append(("hv/%s" % key, val))
3514

    
3515
    # beparams changes
3516
    if self.op.beparams:
3517
      self.instance.beparams = self.be_inst
3518
      for key, val in self.op.beparams.iteritems():
3519
        result.append(("be/%s" % key, val))
3520

    
3521
    # OS change
3522
    if self.op.os_name:
3523
      self.instance.os = self.op.os_name
3524

    
3525
    # osparams changes
3526
    if self.op.osparams:
3527
      self.instance.osparams = self.os_inst
3528
      for key, val in self.op.osparams.iteritems():
3529
        result.append(("os/%s" % key, val))
3530

    
3531
    if self.op.offline is None:
3532
      # Ignore
3533
      pass
3534
    elif self.op.offline:
3535
      # Mark instance as offline
3536
      self.cfg.MarkInstanceOffline(self.instance.uuid)
3537
      result.append(("admin_state", constants.ADMINST_OFFLINE))
3538
    else:
3539
      # Mark instance as online, but stopped
3540
      self.cfg.MarkInstanceDown(self.instance.uuid)
3541
      result.append(("admin_state", constants.ADMINST_DOWN))
3542

    
3543
    self.cfg.Update(self.instance, feedback_fn, self.proc.GetECId())
3544

    
3545
    assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
3546
                self.owned_locks(locking.LEVEL_NODE)), \
3547
      "All node locks should have been released by now"
3548

    
3549
    return result
3550

    
3551
  _DISK_CONVERSIONS = {
3552
    (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
3553
    (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
3554
    }
3555

    
3556

    
3557
class LUInstanceChangeGroup(LogicalUnit):
3558
  HPATH = "instance-change-group"
3559
  HTYPE = constants.HTYPE_INSTANCE
3560
  REQ_BGL = False
3561

    
3562
  def ExpandNames(self):
3563
    self.share_locks = ShareAll()
3564

    
3565
    self.needed_locks = {
3566
      locking.LEVEL_NODEGROUP: [],
3567
      locking.LEVEL_NODE: [],
3568
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
3569
      }
3570

    
3571
    self._ExpandAndLockInstance()
3572

    
3573
    if self.op.target_groups:
3574
      self.req_target_uuids = map(self.cfg.LookupNodeGroup,
3575
                                  self.op.target_groups)
3576
    else:
3577
      self.req_target_uuids = None
3578

    
3579
    self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
3580

    
3581
  def DeclareLocks(self, level):
3582
    if level == locking.LEVEL_NODEGROUP:
3583
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3584

    
3585
      if self.req_target_uuids:
3586
        lock_groups = set(self.req_target_uuids)
3587

    
3588
        # Lock all groups used by instance optimistically; this requires going
3589
        # via the node before it's locked, requiring verification later on
3590
        instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
3591
        lock_groups.update(instance_groups)
3592
      else:
3593
        # No target groups, need to lock all of them
3594
        lock_groups = locking.ALL_SET
3595

    
3596
      self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
3597

    
3598
    elif level == locking.LEVEL_NODE:
3599
      if self.req_target_uuids:
3600
        # Lock all nodes used by instances
3601
        self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3602
        self._LockInstancesNodes()
3603

    
3604
        # Lock all nodes in all potential target groups
3605
        lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
3606
                       self.cfg.GetInstanceNodeGroups(self.op.instance_uuid))
3607
        member_nodes = [node_uuid
3608
                        for group in lock_groups
3609
                        for node_uuid in self.cfg.GetNodeGroup(group).members]
3610
        self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3611
      else:
3612
        # Lock all nodes as all groups are potential targets
3613
        self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3614

    
3615
  def CheckPrereq(self):
3616
    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3617
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3618
    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3619

    
3620
    assert (self.req_target_uuids is None or
3621
            owned_groups.issuperset(self.req_target_uuids))
3622
    assert owned_instance_names == set([self.op.instance_name])
3623

    
3624
    # Get instance information
3625
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
3626

    
3627
    # Check if node groups for locked instance are still correct
3628
    assert owned_nodes.issuperset(self.instance.all_nodes), \
3629
      ("Instance %s's nodes changed while we kept the lock" %
3630
       self.op.instance_name)
3631

    
3632
    inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_uuid,
3633
                                          owned_groups)
3634

    
3635
    if self.req_target_uuids:
3636
      # User requested specific target groups
3637
      self.target_uuids = frozenset(self.req_target_uuids)
3638
    else:
3639
      # All groups except those used by the instance are potential targets
3640
      self.target_uuids = owned_groups - inst_groups
3641

    
3642
    conflicting_groups = self.target_uuids & inst_groups
3643
    if conflicting_groups:
3644
      raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
3645
                                 " used by the instance '%s'" %
3646
                                 (utils.CommaJoin(conflicting_groups),
3647
                                  self.op.instance_name),
3648
                                 errors.ECODE_INVAL)
3649

    
3650
    if not self.target_uuids:
3651
      raise errors.OpPrereqError("There are no possible target groups",
3652
                                 errors.ECODE_INVAL)
3653

    
3654
  def BuildHooksEnv(self):
3655
    """Build hooks env.
3656

3657
    """
3658
    assert self.target_uuids
3659

    
3660
    env = {
3661
      "TARGET_GROUPS": " ".join(self.target_uuids),
3662
      }
3663

    
3664
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
3665

    
3666
    return env
3667

    
3668
  def BuildHooksNodes(self):
3669
    """Build hooks nodes.
3670

3671
    """
3672
    mn = self.cfg.GetMasterNode()
3673
    return ([mn], [mn])
3674

    
3675
  def Exec(self, feedback_fn):
3676
    instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
3677

    
3678
    assert instances == [self.op.instance_name], "Instance not locked"
3679

    
3680
    req = iallocator.IAReqGroupChange(instances=instances,
3681
                                      target_groups=list(self.target_uuids))
3682
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
3683

    
3684
    ial.Run(self.op.iallocator)
3685

    
3686
    if not ial.success:
3687
      raise errors.OpPrereqError("Can't compute solution for changing group of"
3688
                                 " instance '%s' using iallocator '%s': %s" %
3689
                                 (self.op.instance_name, self.op.iallocator,
3690
                                  ial.info), errors.ECODE_NORES)
3691

    
3692
    jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
3693

    
3694
    self.LogInfo("Iallocator returned %s job(s) for changing group of"
3695
                 " instance '%s'", len(jobs), self.op.instance_name)
3696

    
3697
    return ResultWithJobs(jobs)