Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance.py @ 34956ece

History | View | Annotate | Download (141.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
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
    if vlan is not None and nic_mode != constants.NIC_MODE_OVS:
187
      raise errors.OpPrereqError("VLAN is given, but network mode is not"
188
                                 " openvswitch", errors.ECODE_INVAL)
189

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

    
208
      elif not netutils.IPAddress.IsValid(ip):
209
        raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
210
                                   errors.ECODE_INVAL)
211

    
212
      nic_ip = ip
213

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

    
219
    # MAC address verification
220
    mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
221
    if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
222
      mac = utils.NormalizeAndValidateMac(mac)
223

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

    
232
    #  Build nic parameters
233
    nicparams = {}
234
    if nic_mode_req:
235
      nicparams[constants.NIC_MODE] = nic_mode
236
    if link:
237
      nicparams[constants.NIC_LINK] = link
238
    if vlan:
239
      nicparams[constants.NIC_VLAN] = vlan
240

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

    
252
  return nics
253

    
254

    
255
def _CheckForConflictingIp(lu, ip, node_uuid):
256
  """In case of conflicting IP address raise error.
257

258
  @type ip: string
259
  @param ip: IP address
260
  @type node_uuid: string
261
  @param node_uuid: node UUID
262

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

    
271
  return (None, None)
272

    
273

    
274
def _ComputeIPolicyInstanceSpecViolation(
275
  ipolicy, instance_spec, disk_template,
276
  _compute_fn=ComputeIPolicySpecViolation):
277
  """Compute if instance specs meets the specs of ipolicy.
278

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

288
  """
289
  mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
290
  cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
291
  disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
292
  disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
293
  nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
294
  spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
295

    
296
  return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
297
                     disk_sizes, spindle_use, disk_template)
298

    
299

    
300
def _CheckOSVariant(os_obj, name):
301
  """Check whether an OS name conforms to the os variants specification.
302

303
  @type os_obj: L{objects.OS}
304
  @param os_obj: OS object to check
305
  @type name: string
306
  @param name: OS name passed by the user, to check for validity
307

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

    
320
  if variant not in os_obj.supported_variants:
321
    raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
322

    
323

    
324
class LUInstanceCreate(LogicalUnit):
325
  """Create an instance.
326

327
  """
328
  HPATH = "instance-add"
329
  HTYPE = constants.HTYPE_INSTANCE
330
  REQ_BGL = False
331

    
332
  def _CheckDiskTemplateValid(self):
333
    """Checks validity of disk template.
334

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

    
346
  def _CheckDiskArguments(self):
347
    """Checks validity of disk-related arguments.
348

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

    
353
    self._CheckDiskTemplateValid()
354

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

    
386
    self.adopt_disks = has_adopt
387

    
388
  def _CheckVLANArguments(self):
389
    """ Check validity of VLANs if given
390

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

    
420
  def CheckArguments(self):
421
    """Check arguments.
422

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

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

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

    
444
    self._CheckVLANArguments()
445

    
446
    self._CheckDiskArguments()
447
    assert self.op.disk_template is not None
448

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

    
458
    ### Node/iallocator related checks
459
    CheckIAllocatorOrNode(self, "iallocator", "pnode")
460

    
461
    if self.op.pnode is not None:
462
      if self.op.disk_template in constants.DTS_INT_MIRROR:
463
        if self.op.snode is None:
464
          raise errors.OpPrereqError("The networked disk templates need"
465
                                     " a mirror node", errors.ECODE_INVAL)
466
      elif self.op.snode:
467
        self.LogWarning("Secondary node will be ignored on non-mirrored disk"
468
                        " template")
469
        self.op.snode = None
470

    
471
    _CheckOpportunisticLocking(self.op)
472

    
473
    if self.op.mode == constants.INSTANCE_IMPORT:
474
      # On import force_variant must be True, because if we forced it at
475
      # initial install, our only chance when importing it back is that it
476
      # works again!
477
      self.op.force_variant = True
478

    
479
      if self.op.no_install:
480
        self.LogInfo("No-installation mode has no effect during import")
481

    
482
    elif self.op.mode == constants.INSTANCE_CREATE:
483
      if self.op.os_type is None:
484
        raise errors.OpPrereqError("No guest OS specified",
485
                                   errors.ECODE_INVAL)
486
      if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
487
        raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
488
                                   " installation" % self.op.os_type,
489
                                   errors.ECODE_STATE)
490
    elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
491
      self._cds = GetClusterDomainSecret()
492

    
493
      # Check handshake to ensure both clusters have the same domain secret
494
      src_handshake = self.op.source_handshake
495
      if not src_handshake:
496
        raise errors.OpPrereqError("Missing source handshake",
497
                                   errors.ECODE_INVAL)
498

    
499
      errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
500
                                                           src_handshake)
501
      if errmsg:
502
        raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
503
                                   errors.ECODE_INVAL)
504

    
505
      # Load and check source CA
506
      self.source_x509_ca_pem = self.op.source_x509_ca
507
      if not self.source_x509_ca_pem:
508
        raise errors.OpPrereqError("Missing source X509 CA",
509
                                   errors.ECODE_INVAL)
510

    
511
      try:
512
        (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
513
                                                    self._cds)
514
      except OpenSSL.crypto.Error, err:
515
        raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
516
                                   (err, ), errors.ECODE_INVAL)
517

    
518
      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
519
      if errcode is not None:
520
        raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
521
                                   errors.ECODE_INVAL)
522

    
523
      self.source_x509_ca = cert
524

    
525
      src_instance_name = self.op.source_instance_name
526
      if not src_instance_name:
527
        raise errors.OpPrereqError("Missing source instance name",
528
                                   errors.ECODE_INVAL)
529

    
530
      self.source_instance_name = \
531
        netutils.GetHostname(name=src_instance_name).name
532

    
533
    else:
534
      raise errors.OpPrereqError("Invalid instance creation mode %r" %
535
                                 self.op.mode, errors.ECODE_INVAL)
536

    
537
  def ExpandNames(self):
538
    """ExpandNames for CreateInstance.
539

540
    Figure out the right locks for instance creation.
541

542
    """
543
    self.needed_locks = {}
544

    
545
    # this is just a preventive check, but someone might still add this
546
    # instance in the meantime, and creation will fail at lock-add time
547
    if self.op.instance_name in\
548
      [inst.name for inst in self.cfg.GetAllInstancesInfo().values()]:
549
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
550
                                 self.op.instance_name, errors.ECODE_EXISTS)
551

    
552
    self.add_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
553

    
554
    if self.op.iallocator:
555
      # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
556
      # specifying a group on instance creation and then selecting nodes from
557
      # that group
558
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
559
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
560

    
561
      if self.op.opportunistic_locking:
562
        self.opportunistic_locks[locking.LEVEL_NODE] = True
563
        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
564
    else:
565
      (self.op.pnode_uuid, self.op.pnode) = \
566
        ExpandNodeUuidAndName(self.cfg, self.op.pnode_uuid, self.op.pnode)
567
      nodelist = [self.op.pnode_uuid]
568
      if self.op.snode is not None:
569
        (self.op.snode_uuid, self.op.snode) = \
570
          ExpandNodeUuidAndName(self.cfg, self.op.snode_uuid, self.op.snode)
571
        nodelist.append(self.op.snode_uuid)
572
      self.needed_locks[locking.LEVEL_NODE] = nodelist
573

    
574
    # in case of import lock the source node too
575
    if self.op.mode == constants.INSTANCE_IMPORT:
576
      src_node = self.op.src_node
577
      src_path = self.op.src_path
578

    
579
      if src_path is None:
580
        self.op.src_path = src_path = self.op.instance_name
581

    
582
      if src_node is None:
583
        self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
584
        self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
585
        self.op.src_node = None
586
        if os.path.isabs(src_path):
587
          raise errors.OpPrereqError("Importing an instance from a path"
588
                                     " requires a source node option",
589
                                     errors.ECODE_INVAL)
590
      else:
591
        (self.op.src_node_uuid, self.op.src_node) = (_, src_node) = \
592
          ExpandNodeUuidAndName(self.cfg, self.op.src_node_uuid, src_node)
593
        if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
594
          self.needed_locks[locking.LEVEL_NODE].append(self.op.src_node_uuid)
595
        if not os.path.isabs(src_path):
596
          self.op.src_path = \
597
            utils.PathJoin(pathutils.EXPORT_DIR, src_path)
598

    
599
    self.needed_locks[locking.LEVEL_NODE_RES] = \
600
      CopyLockList(self.needed_locks[locking.LEVEL_NODE])
601

    
602
  def _RunAllocator(self):
603
    """Run the allocator based on input opcode.
604

605
    """
606
    if self.op.opportunistic_locking:
607
      # Only consider nodes for which a lock is held
608
      node_name_whitelist = self.cfg.GetNodeNames(
609
        self.owned_locks(locking.LEVEL_NODE))
610
    else:
611
      node_name_whitelist = None
612

    
613
    req = _CreateInstanceAllocRequest(self.op, self.disks,
614
                                      self.nics, self.be_full,
615
                                      node_name_whitelist)
616
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
617

    
618
    ial.Run(self.op.iallocator)
619

    
620
    if not ial.success:
621
      # When opportunistic locks are used only a temporary failure is generated
622
      if self.op.opportunistic_locking:
623
        ecode = errors.ECODE_TEMP_NORES
624
      else:
625
        ecode = errors.ECODE_NORES
626

    
627
      raise errors.OpPrereqError("Can't compute nodes using"
628
                                 " iallocator '%s': %s" %
629
                                 (self.op.iallocator, ial.info),
630
                                 ecode)
631

    
632
    (self.op.pnode_uuid, self.op.pnode) = \
633
      ExpandNodeUuidAndName(self.cfg, None, ial.result[0])
634
    self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
635
                 self.op.instance_name, self.op.iallocator,
636
                 utils.CommaJoin(ial.result))
637

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

    
640
    if req.RequiredNodes() == 2:
641
      (self.op.snode_uuid, self.op.snode) = \
642
        ExpandNodeUuidAndName(self.cfg, None, ial.result[1])
643

    
644
  def BuildHooksEnv(self):
645
    """Build hooks env.
646

647
    This runs on master, primary and secondary nodes of the instance.
648

649
    """
650
    env = {
651
      "ADD_MODE": self.op.mode,
652
      }
653
    if self.op.mode == constants.INSTANCE_IMPORT:
654
      env["SRC_NODE"] = self.op.src_node
655
      env["SRC_PATH"] = self.op.src_path
656
      env["SRC_IMAGES"] = self.src_images
657

    
658
    env.update(BuildInstanceHookEnv(
659
      name=self.op.instance_name,
660
      primary_node_name=self.op.pnode,
661
      secondary_node_names=self.cfg.GetNodeNames(self.secondaries),
662
      status=self.op.start,
663
      os_type=self.op.os_type,
664
      minmem=self.be_full[constants.BE_MINMEM],
665
      maxmem=self.be_full[constants.BE_MAXMEM],
666
      vcpus=self.be_full[constants.BE_VCPUS],
667
      nics=NICListToTuple(self, self.nics),
668
      disk_template=self.op.disk_template,
669
      disks=[(d[constants.IDISK_NAME], d.get("uuid", ""),
670
              d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
671
             for d in self.disks],
672
      bep=self.be_full,
673
      hvp=self.hv_full,
674
      hypervisor_name=self.op.hypervisor,
675
      tags=self.op.tags,
676
      ))
677

    
678
    return env
679

    
680
  def BuildHooksNodes(self):
681
    """Build hooks nodes.
682

683
    """
684
    nl = [self.cfg.GetMasterNode(), self.op.pnode_uuid] + self.secondaries
685
    return nl, nl
686

    
687
  def _ReadExportInfo(self):
688
    """Reads the export information from disk.
689

690
    It will override the opcode source node and path with the actual
691
    information, if these two were not specified before.
692

693
    @return: the export information
694

695
    """
696
    assert self.op.mode == constants.INSTANCE_IMPORT
697

    
698
    if self.op.src_node_uuid is None:
699
      locked_nodes = self.owned_locks(locking.LEVEL_NODE)
700
      exp_list = self.rpc.call_export_list(locked_nodes)
701
      found = False
702
      for node_uuid in exp_list:
703
        if exp_list[node_uuid].fail_msg:
704
          continue
705
        if self.op.src_path in exp_list[node_uuid].payload:
706
          found = True
707
          self.op.src_node = self.cfg.GetNodeInfo(node_uuid).name
708
          self.op.src_node_uuid = node_uuid
709
          self.op.src_path = utils.PathJoin(pathutils.EXPORT_DIR,
710
                                            self.op.src_path)
711
          break
712
      if not found:
713
        raise errors.OpPrereqError("No export found for relative path %s" %
714
                                   self.op.src_path, errors.ECODE_INVAL)
715

    
716
    CheckNodeOnline(self, self.op.src_node_uuid)
717
    result = self.rpc.call_export_info(self.op.src_node_uuid, self.op.src_path)
718
    result.Raise("No export or invalid export found in dir %s" %
719
                 self.op.src_path)
720

    
721
    export_info = objects.SerializableConfigParser.Loads(str(result.payload))
722
    if not export_info.has_section(constants.INISECT_EXP):
723
      raise errors.ProgrammerError("Corrupted export config",
724
                                   errors.ECODE_ENVIRON)
725

    
726
    ei_version = export_info.get(constants.INISECT_EXP, "version")
727
    if int(ei_version) != constants.EXPORT_VERSION:
728
      raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
729
                                 (ei_version, constants.EXPORT_VERSION),
730
                                 errors.ECODE_ENVIRON)
731
    return export_info
732

    
733
  def _ReadExportParams(self, einfo):
734
    """Use export parameters as defaults.
735

736
    In case the opcode doesn't specify (as in override) some instance
737
    parameters, then try to use them from the export information, if
738
    that declares them.
739

740
    """
741
    self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
742

    
743
    if not self.op.disks:
744
      disks = []
745
      # TODO: import the disk iv_name too
746
      for idx in range(constants.MAX_DISKS):
747
        if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
748
          disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
749
          disks.append({constants.IDISK_SIZE: disk_sz})
750
      self.op.disks = disks
751
      if not disks and self.op.disk_template != constants.DT_DISKLESS:
752
        raise errors.OpPrereqError("No disk info specified and the export"
753
                                   " is missing the disk information",
754
                                   errors.ECODE_INVAL)
755

    
756
    if not self.op.nics:
757
      nics = []
758
      for idx in range(constants.MAX_NICS):
759
        if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
760
          ndict = {}
761
          for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
762
            nic_param_name = "nic%d_%s" % (idx, name)
763
            if einfo.has_option(constants.INISECT_INS, nic_param_name):
764
              v = einfo.get(constants.INISECT_INS, nic_param_name)
765
              ndict[name] = v
766
          nics.append(ndict)
767
        else:
768
          break
769
      self.op.nics = nics
770

    
771
    if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
772
      self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
773

    
774
    if (self.op.hypervisor is None and
775
        einfo.has_option(constants.INISECT_INS, "hypervisor")):
776
      self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
777

    
778
    if einfo.has_section(constants.INISECT_HYP):
779
      # use the export parameters but do not override the ones
780
      # specified by the user
781
      for name, value in einfo.items(constants.INISECT_HYP):
782
        if name not in self.op.hvparams:
783
          self.op.hvparams[name] = value
784

    
785
    if einfo.has_section(constants.INISECT_BEP):
786
      # use the parameters, without overriding
787
      for name, value in einfo.items(constants.INISECT_BEP):
788
        if name not in self.op.beparams:
789
          self.op.beparams[name] = value
790
        # Compatibility for the old "memory" be param
791
        if name == constants.BE_MEMORY:
792
          if constants.BE_MAXMEM not in self.op.beparams:
793
            self.op.beparams[constants.BE_MAXMEM] = value
794
          if constants.BE_MINMEM not in self.op.beparams:
795
            self.op.beparams[constants.BE_MINMEM] = value
796
    else:
797
      # try to read the parameters old style, from the main section
798
      for name in constants.BES_PARAMETERS:
799
        if (name not in self.op.beparams and
800
            einfo.has_option(constants.INISECT_INS, name)):
801
          self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
802

    
803
    if einfo.has_section(constants.INISECT_OSP):
804
      # use the parameters, without overriding
805
      for name, value in einfo.items(constants.INISECT_OSP):
806
        if name not in self.op.osparams:
807
          self.op.osparams[name] = value
808

    
809
  def _RevertToDefaults(self, cluster):
810
    """Revert the instance parameters to the default values.
811

812
    """
813
    # hvparams
814
    hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
815
    for name in self.op.hvparams.keys():
816
      if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
817
        del self.op.hvparams[name]
818
    # beparams
819
    be_defs = cluster.SimpleFillBE({})
820
    for name in self.op.beparams.keys():
821
      if name in be_defs and be_defs[name] == self.op.beparams[name]:
822
        del self.op.beparams[name]
823
    # nic params
824
    nic_defs = cluster.SimpleFillNIC({})
825
    for nic in self.op.nics:
826
      for name in constants.NICS_PARAMETERS:
827
        if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
828
          del nic[name]
829
    # osparams
830
    os_defs = cluster.SimpleFillOS(self.op.os_type, {})
831
    for name in self.op.osparams.keys():
832
      if name in os_defs and os_defs[name] == self.op.osparams[name]:
833
        del self.op.osparams[name]
834

    
835
  def _CalculateFileStorageDir(self):
836
    """Calculate final instance file storage dir.
837

838
    """
839
    # file storage dir calculation/check
840
    self.instance_file_storage_dir = None
841
    if self.op.disk_template in constants.DTS_FILEBASED:
842
      # build the full file storage dir path
843
      joinargs = []
844

    
845
      if self.op.disk_template == constants.DT_SHARED_FILE:
846
        get_fsd_fn = self.cfg.GetSharedFileStorageDir
847
      else:
848
        get_fsd_fn = self.cfg.GetFileStorageDir
849

    
850
      cfg_storagedir = get_fsd_fn()
851
      if not cfg_storagedir:
852
        raise errors.OpPrereqError("Cluster file storage dir not defined",
853
                                   errors.ECODE_STATE)
854
      joinargs.append(cfg_storagedir)
855

    
856
      if self.op.file_storage_dir is not None:
857
        joinargs.append(self.op.file_storage_dir)
858

    
859
      joinargs.append(self.op.instance_name)
860

    
861
      # pylint: disable=W0142
862
      self.instance_file_storage_dir = utils.PathJoin(*joinargs)
863

    
864
  def CheckPrereq(self): # pylint: disable=R0914
865
    """Check prerequisites.
866

867
    """
868
    self._CalculateFileStorageDir()
869

    
870
    if self.op.mode == constants.INSTANCE_IMPORT:
871
      export_info = self._ReadExportInfo()
872
      self._ReadExportParams(export_info)
873
      self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
874
    else:
875
      self._old_instance_name = None
876

    
877
    if (not self.cfg.GetVGName() and
878
        self.op.disk_template not in constants.DTS_NOT_LVM):
879
      raise errors.OpPrereqError("Cluster does not support lvm-based"
880
                                 " instances", errors.ECODE_STATE)
881

    
882
    if (self.op.hypervisor is None or
883
        self.op.hypervisor == constants.VALUE_AUTO):
884
      self.op.hypervisor = self.cfg.GetHypervisorType()
885

    
886
    cluster = self.cfg.GetClusterInfo()
887
    enabled_hvs = cluster.enabled_hypervisors
888
    if self.op.hypervisor not in enabled_hvs:
889
      raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
890
                                 " cluster (%s)" %
891
                                 (self.op.hypervisor, ",".join(enabled_hvs)),
892
                                 errors.ECODE_STATE)
893

    
894
    # Check tag validity
895
    for tag in self.op.tags:
896
      objects.TaggableObject.ValidateTag(tag)
897

    
898
    # check hypervisor parameter syntax (locally)
899
    utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
900
    filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
901
                                      self.op.hvparams)
902
    hv_type = hypervisor.GetHypervisorClass(self.op.hypervisor)
903
    hv_type.CheckParameterSyntax(filled_hvp)
904
    self.hv_full = filled_hvp
905
    # check that we don't specify global parameters on an instance
906
    CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, "hypervisor",
907
                         "instance", "cluster")
908

    
909
    # fill and remember the beparams dict
910
    self.be_full = _ComputeFullBeParams(self.op, cluster)
911

    
912
    # build os parameters
913
    self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
914

    
915
    # now that hvp/bep are in final format, let's reset to defaults,
916
    # if told to do so
917
    if self.op.identify_defaults:
918
      self._RevertToDefaults(cluster)
919

    
920
    # NIC buildup
921
    self.nics = _ComputeNics(self.op, cluster, self.check_ip, self.cfg,
922
                             self.proc.GetECId())
923

    
924
    # disk checks/pre-build
925
    default_vg = self.cfg.GetVGName()
926
    self.disks = ComputeDisks(self.op, default_vg)
927

    
928
    if self.op.mode == constants.INSTANCE_IMPORT:
929
      disk_images = []
930
      for idx in range(len(self.disks)):
931
        option = "disk%d_dump" % idx
932
        if export_info.has_option(constants.INISECT_INS, option):
933
          # FIXME: are the old os-es, disk sizes, etc. useful?
934
          export_name = export_info.get(constants.INISECT_INS, option)
935
          image = utils.PathJoin(self.op.src_path, export_name)
936
          disk_images.append(image)
937
        else:
938
          disk_images.append(False)
939

    
940
      self.src_images = disk_images
941

    
942
      if self.op.instance_name == self._old_instance_name:
943
        for idx, nic in enumerate(self.nics):
944
          if nic.mac == constants.VALUE_AUTO:
945
            nic_mac_ini = "nic%d_mac" % idx
946
            nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
947

    
948
    # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
949

    
950
    # ip ping checks (we use the same ip that was resolved in ExpandNames)
951
    if self.op.ip_check:
952
      if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
953
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
954
                                   (self.check_ip, self.op.instance_name),
955
                                   errors.ECODE_NOTUNIQUE)
956

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

    
969
    #### allocator run
970

    
971
    if self.op.iallocator is not None:
972
      self._RunAllocator()
973

    
974
    # Release all unneeded node locks
975
    keep_locks = filter(None, [self.op.pnode_uuid, self.op.snode_uuid,
976
                               self.op.src_node_uuid])
977
    ReleaseLocks(self, locking.LEVEL_NODE, keep=keep_locks)
978
    ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=keep_locks)
979
    ReleaseLocks(self, locking.LEVEL_NODE_ALLOC)
980

    
981
    assert (self.owned_locks(locking.LEVEL_NODE) ==
982
            self.owned_locks(locking.LEVEL_NODE_RES)), \
983
      "Node locks differ from node resource locks"
984

    
985
    #### node related checks
986

    
987
    # check primary node
988
    self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode_uuid)
989
    assert self.pnode is not None, \
990
      "Cannot retrieve locked node %s" % self.op.pnode_uuid
991
    if pnode.offline:
992
      raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
993
                                 pnode.name, errors.ECODE_STATE)
994
    if pnode.drained:
995
      raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
996
                                 pnode.name, errors.ECODE_STATE)
997
    if not pnode.vm_capable:
998
      raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
999
                                 " '%s'" % pnode.name, errors.ECODE_STATE)
1000

    
1001
    self.secondaries = []
1002

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

    
1037
      # net is None, ip None or given
1038
      elif self.op.conflicts_check:
1039
        _CheckForConflictingIp(self, nic.ip, self.pnode.uuid)
1040

    
1041
    # mirror node verification
1042
    if self.op.disk_template in constants.DTS_INT_MIRROR:
1043
      if self.op.snode_uuid == pnode.uuid:
1044
        raise errors.OpPrereqError("The secondary node cannot be the"
1045
                                   " primary node", errors.ECODE_INVAL)
1046
      CheckNodeOnline(self, self.op.snode_uuid)
1047
      CheckNodeNotDrained(self, self.op.snode_uuid)
1048
      CheckNodeVmCapable(self, self.op.snode_uuid)
1049
      self.secondaries.append(self.op.snode_uuid)
1050

    
1051
      snode = self.cfg.GetNodeInfo(self.op.snode_uuid)
1052
      if pnode.group != snode.group:
1053
        self.LogWarning("The primary and secondary nodes are in two"
1054
                        " different node groups; the disk parameters"
1055
                        " from the first disk's node group will be"
1056
                        " used")
1057

    
1058
    nodes = [pnode]
1059
    if self.op.disk_template in constants.DTS_INT_MIRROR:
1060
      nodes.append(snode)
1061
    has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
1062
    excl_stor = compat.any(map(has_es, nodes))
1063
    if excl_stor and not self.op.disk_template in constants.DTS_EXCL_STORAGE:
1064
      raise errors.OpPrereqError("Disk template %s not supported with"
1065
                                 " exclusive storage" % self.op.disk_template,
1066
                                 errors.ECODE_STATE)
1067
    for disk in self.disks:
1068
      CheckSpindlesExclusiveStorage(disk, excl_stor, True)
1069

    
1070
    node_uuids = [pnode.uuid] + self.secondaries
1071

    
1072
    if not self.adopt_disks:
1073
      if self.op.disk_template == constants.DT_RBD:
1074
        # _CheckRADOSFreeSpace() is just a placeholder.
1075
        # Any function that checks prerequisites can be placed here.
1076
        # Check if there is enough space on the RADOS cluster.
1077
        CheckRADOSFreeSpace()
1078
      elif self.op.disk_template == constants.DT_EXT:
1079
        # FIXME: Function that checks prereqs if needed
1080
        pass
1081
      elif self.op.disk_template in utils.GetLvmDiskTemplates():
1082
        # Check lv size requirements, if not adopting
1083
        req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
1084
        CheckNodesFreeDiskPerVG(self, node_uuids, req_sizes)
1085
      else:
1086
        # FIXME: add checks for other, non-adopting, non-lvm disk templates
1087
        pass
1088

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

    
1105
      vg_names = self.rpc.call_vg_list([pnode.uuid])[pnode.uuid]
1106
      vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
1107

    
1108
      node_lvs = self.rpc.call_lv_list([pnode.uuid],
1109
                                       vg_names.payload.keys())[pnode.uuid]
1110
      node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
1111
      node_lvs = node_lvs.payload
1112

    
1113
      delta = all_lvs.difference(node_lvs.keys())
1114
      if delta:
1115
        raise errors.OpPrereqError("Missing logical volume(s): %s" %
1116
                                   utils.CommaJoin(delta),
1117
                                   errors.ECODE_INVAL)
1118
      online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
1119
      if online_lvs:
1120
        raise errors.OpPrereqError("Online logical volumes found, cannot"
1121
                                   " adopt: %s" % utils.CommaJoin(online_lvs),
1122
                                   errors.ECODE_STATE)
1123
      # update the size of disk based on what is found
1124
      for dsk in self.disks:
1125
        dsk[constants.IDISK_SIZE] = \
1126
          int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
1127
                                        dsk[constants.IDISK_ADOPT])][0]))
1128

    
1129
    elif self.op.disk_template == constants.DT_BLOCK:
1130
      # Normalize and de-duplicate device paths
1131
      all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
1132
                       for disk in self.disks])
1133
      if len(all_disks) != len(self.disks):
1134
        raise errors.OpPrereqError("Duplicate disk names given for adoption",
1135
                                   errors.ECODE_INVAL)
1136
      baddisks = [d for d in all_disks
1137
                  if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
1138
      if baddisks:
1139
        raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
1140
                                   " cannot be adopted" %
1141
                                   (utils.CommaJoin(baddisks),
1142
                                    constants.ADOPTABLE_BLOCKDEV_ROOT),
1143
                                   errors.ECODE_INVAL)
1144

    
1145
      node_disks = self.rpc.call_bdev_sizes([pnode.uuid],
1146
                                            list(all_disks))[pnode.uuid]
1147
      node_disks.Raise("Cannot get block device information from node %s" %
1148
                       pnode.name)
1149
      node_disks = node_disks.payload
1150
      delta = all_disks.difference(node_disks.keys())
1151
      if delta:
1152
        raise errors.OpPrereqError("Missing block device(s): %s" %
1153
                                   utils.CommaJoin(delta),
1154
                                   errors.ECODE_INVAL)
1155
      for dsk in self.disks:
1156
        dsk[constants.IDISK_SIZE] = \
1157
          int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
1158

    
1159
    # Verify instance specs
1160
    spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
1161
    ispec = {
1162
      constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
1163
      constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
1164
      constants.ISPEC_DISK_COUNT: len(self.disks),
1165
      constants.ISPEC_DISK_SIZE: [disk[constants.IDISK_SIZE]
1166
                                  for disk in self.disks],
1167
      constants.ISPEC_NIC_COUNT: len(self.nics),
1168
      constants.ISPEC_SPINDLE_USE: spindle_use,
1169
      }
1170

    
1171
    group_info = self.cfg.GetNodeGroup(pnode.group)
1172
    ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1173
    res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec,
1174
                                               self.op.disk_template)
1175
    if not self.op.ignore_ipolicy and res:
1176
      msg = ("Instance allocation to group %s (%s) violates policy: %s" %
1177
             (pnode.group, group_info.name, utils.CommaJoin(res)))
1178
      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1179

    
1180
    CheckHVParams(self, node_uuids, self.op.hypervisor, self.op.hvparams)
1181

    
1182
    CheckNodeHasOS(self, pnode.uuid, self.op.os_type, self.op.force_variant)
1183
    # check OS parameters (remotely)
1184
    CheckOSParams(self, True, node_uuids, self.op.os_type, self.os_full)
1185

    
1186
    CheckNicsBridgesExist(self, self.nics, self.pnode.uuid)
1187

    
1188
    #TODO: _CheckExtParams (remotely)
1189
    # Check parameters for extstorage
1190

    
1191
    # memory check on primary node
1192
    #TODO(dynmem): use MINMEM for checking
1193
    if self.op.start:
1194
      hvfull = objects.FillDict(cluster.hvparams.get(self.op.hypervisor, {}),
1195
                                self.op.hvparams)
1196
      CheckNodeFreeMemory(self, self.pnode.uuid,
1197
                          "creating instance %s" % self.op.instance_name,
1198
                          self.be_full[constants.BE_MAXMEM],
1199
                          self.op.hypervisor, hvfull)
1200

    
1201
    self.dry_run_result = list(node_uuids)
1202

    
1203
  def Exec(self, feedback_fn):
1204
    """Create and add the instance to the cluster.
1205

1206
    """
1207
    assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
1208
                self.owned_locks(locking.LEVEL_NODE)), \
1209
      "Node locks differ from node resource locks"
1210
    assert not self.glm.is_owned(locking.LEVEL_NODE_ALLOC)
1211

    
1212
    ht_kind = self.op.hypervisor
1213
    if ht_kind in constants.HTS_REQ_PORT:
1214
      network_port = self.cfg.AllocatePort()
1215
    else:
1216
      network_port = None
1217

    
1218
    instance_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
1219

    
1220
    # This is ugly but we got a chicken-egg problem here
1221
    # We can only take the group disk parameters, as the instance
1222
    # has no disks yet (we are generating them right here).
1223
    nodegroup = self.cfg.GetNodeGroup(self.pnode.group)
1224
    disks = GenerateDiskTemplate(self,
1225
                                 self.op.disk_template,
1226
                                 instance_uuid, self.pnode.uuid,
1227
                                 self.secondaries,
1228
                                 self.disks,
1229
                                 self.instance_file_storage_dir,
1230
                                 self.op.file_driver,
1231
                                 0,
1232
                                 feedback_fn,
1233
                                 self.cfg.GetGroupDiskParams(nodegroup))
1234

    
1235
    iobj = objects.Instance(name=self.op.instance_name,
1236
                            uuid=instance_uuid,
1237
                            os=self.op.os_type,
1238
                            primary_node=self.pnode.uuid,
1239
                            nics=self.nics, disks=disks,
1240
                            disk_template=self.op.disk_template,
1241
                            disks_active=False,
1242
                            admin_state=constants.ADMINST_DOWN,
1243
                            network_port=network_port,
1244
                            beparams=self.op.beparams,
1245
                            hvparams=self.op.hvparams,
1246
                            hypervisor=self.op.hypervisor,
1247
                            osparams=self.op.osparams,
1248
                            )
1249

    
1250
    if self.op.tags:
1251
      for tag in self.op.tags:
1252
        iobj.AddTag(tag)
1253

    
1254
    if self.adopt_disks:
1255
      if self.op.disk_template == constants.DT_PLAIN:
1256
        # rename LVs to the newly-generated names; we need to construct
1257
        # 'fake' LV disks with the old data, plus the new unique_id
1258
        tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
1259
        rename_to = []
1260
        for t_dsk, a_dsk in zip(tmp_disks, self.disks):
1261
          rename_to.append(t_dsk.logical_id)
1262
          t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
1263
          self.cfg.SetDiskID(t_dsk, self.pnode.uuid)
1264
        result = self.rpc.call_blockdev_rename(self.pnode.uuid,
1265
                                               zip(tmp_disks, rename_to))
1266
        result.Raise("Failed to rename adoped LVs")
1267
    else:
1268
      feedback_fn("* creating instance disks...")
1269
      try:
1270
        CreateDisks(self, iobj)
1271
      except errors.OpExecError:
1272
        self.LogWarning("Device creation failed")
1273
        self.cfg.ReleaseDRBDMinors(self.op.instance_name)
1274
        raise
1275

    
1276
    feedback_fn("adding instance %s to cluster config" % self.op.instance_name)
1277

    
1278
    self.cfg.AddInstance(iobj, self.proc.GetECId())
1279

    
1280
    # Declare that we don't want to remove the instance lock anymore, as we've
1281
    # added the instance to the config
1282
    del self.remove_locks[locking.LEVEL_INSTANCE]
1283

    
1284
    if self.op.mode == constants.INSTANCE_IMPORT:
1285
      # Release unused nodes
1286
      ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node_uuid])
1287
    else:
1288
      # Release all nodes
1289
      ReleaseLocks(self, locking.LEVEL_NODE)
1290

    
1291
    disk_abort = False
1292
    if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
1293
      feedback_fn("* wiping instance disks...")
1294
      try:
1295
        WipeDisks(self, iobj)
1296
      except errors.OpExecError, err:
1297
        logging.exception("Wiping disks failed")
1298
        self.LogWarning("Wiping instance disks failed (%s)", err)
1299
        disk_abort = True
1300

    
1301
    if disk_abort:
1302
      # Something is already wrong with the disks, don't do anything else
1303
      pass
1304
    elif self.op.wait_for_sync:
1305
      disk_abort = not WaitForSync(self, iobj)
1306
    elif iobj.disk_template in constants.DTS_INT_MIRROR:
1307
      # make sure the disks are not degraded (still sync-ing is ok)
1308
      feedback_fn("* checking mirrors status")
1309
      disk_abort = not WaitForSync(self, iobj, oneshot=True)
1310
    else:
1311
      disk_abort = False
1312

    
1313
    if disk_abort:
1314
      RemoveDisks(self, iobj)
1315
      self.cfg.RemoveInstance(iobj.uuid)
1316
      # Make sure the instance lock gets removed
1317
      self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
1318
      raise errors.OpExecError("There are some degraded disks for"
1319
                               " this instance")
1320

    
1321
    # instance disks are now active
1322
    iobj.disks_active = True
1323

    
1324
    # Release all node resource locks
1325
    ReleaseLocks(self, locking.LEVEL_NODE_RES)
1326

    
1327
    if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
1328
      # we need to set the disks ID to the primary node, since the
1329
      # preceding code might or might have not done it, depending on
1330
      # disk template and other options
1331
      for disk in iobj.disks:
1332
        self.cfg.SetDiskID(disk, self.pnode.uuid)
1333
      if self.op.mode == constants.INSTANCE_CREATE:
1334
        if not self.op.no_install:
1335
          pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
1336
                        not self.op.wait_for_sync)
1337
          if pause_sync:
1338
            feedback_fn("* pausing disk sync to install instance OS")
1339
            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
1340
                                                              (iobj.disks,
1341
                                                               iobj), True)
1342
            for idx, success in enumerate(result.payload):
1343
              if not success:
1344
                logging.warn("pause-sync of instance %s for disk %d failed",
1345
                             self.op.instance_name, idx)
1346

    
1347
          feedback_fn("* running the instance OS create scripts...")
1348
          # FIXME: pass debug option from opcode to backend
1349
          os_add_result = \
1350
            self.rpc.call_instance_os_add(self.pnode.uuid, (iobj, None), False,
1351
                                          self.op.debug_level)
1352
          if pause_sync:
1353
            feedback_fn("* resuming disk sync")
1354
            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
1355
                                                              (iobj.disks,
1356
                                                               iobj), False)
1357
            for idx, success in enumerate(result.payload):
1358
              if not success:
1359
                logging.warn("resume-sync of instance %s for disk %d failed",
1360
                             self.op.instance_name, idx)
1361

    
1362
          os_add_result.Raise("Could not add os for instance %s"
1363
                              " on node %s" % (self.op.instance_name,
1364
                                               self.pnode.name))
1365

    
1366
      else:
1367
        if self.op.mode == constants.INSTANCE_IMPORT:
1368
          feedback_fn("* running the instance OS import scripts...")
1369

    
1370
          transfers = []
1371

    
1372
          for idx, image in enumerate(self.src_images):
1373
            if not image:
1374
              continue
1375

    
1376
            # FIXME: pass debug option from opcode to backend
1377
            dt = masterd.instance.DiskTransfer("disk/%s" % idx,
1378
                                               constants.IEIO_FILE, (image, ),
1379
                                               constants.IEIO_SCRIPT,
1380
                                               (iobj.disks[idx], idx),
1381
                                               None)
1382
            transfers.append(dt)
1383

    
1384
          import_result = \
1385
            masterd.instance.TransferInstanceData(self, feedback_fn,
1386
                                                  self.op.src_node_uuid,
1387
                                                  self.pnode.uuid,
1388
                                                  self.pnode.secondary_ip,
1389
                                                  iobj, transfers)
1390
          if not compat.all(import_result):
1391
            self.LogWarning("Some disks for instance %s on node %s were not"
1392
                            " imported successfully" % (self.op.instance_name,
1393
                                                        self.pnode.name))
1394

    
1395
          rename_from = self._old_instance_name
1396

    
1397
        elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
1398
          feedback_fn("* preparing remote import...")
1399
          # The source cluster will stop the instance before attempting to make
1400
          # a connection. In some cases stopping an instance can take a long
1401
          # time, hence the shutdown timeout is added to the connection
1402
          # timeout.
1403
          connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
1404
                             self.op.source_shutdown_timeout)
1405
          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
1406

    
1407
          assert iobj.primary_node == self.pnode.uuid
1408
          disk_results = \
1409
            masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
1410
                                          self.source_x509_ca,
1411
                                          self._cds, timeouts)
1412
          if not compat.all(disk_results):
1413
            # TODO: Should the instance still be started, even if some disks
1414
            # failed to import (valid for local imports, too)?
1415
            self.LogWarning("Some disks for instance %s on node %s were not"
1416
                            " imported successfully" % (self.op.instance_name,
1417
                                                        self.pnode.name))
1418

    
1419
          rename_from = self.source_instance_name
1420

    
1421
        else:
1422
          # also checked in the prereq part
1423
          raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
1424
                                       % self.op.mode)
1425

    
1426
        # Run rename script on newly imported instance
1427
        assert iobj.name == self.op.instance_name
1428
        feedback_fn("Running rename script for %s" % self.op.instance_name)
1429
        result = self.rpc.call_instance_run_rename(self.pnode.uuid, iobj,
1430
                                                   rename_from,
1431
                                                   self.op.debug_level)
1432
        result.Warn("Failed to run rename script for %s on node %s" %
1433
                    (self.op.instance_name, self.pnode.name), self.LogWarning)
1434

    
1435
    assert not self.owned_locks(locking.LEVEL_NODE_RES)
1436

    
1437
    if self.op.start:
1438
      iobj.admin_state = constants.ADMINST_UP
1439
      self.cfg.Update(iobj, feedback_fn)
1440
      logging.info("Starting instance %s on node %s", self.op.instance_name,
1441
                   self.pnode.name)
1442
      feedback_fn("* starting instance...")
1443
      result = self.rpc.call_instance_start(self.pnode.uuid, (iobj, None, None),
1444
                                            False, self.op.reason)
1445
      result.Raise("Could not start instance")
1446

    
1447
    return list(iobj.all_nodes)
1448

    
1449

    
1450
class LUInstanceRename(LogicalUnit):
1451
  """Rename an instance.
1452

1453
  """
1454
  HPATH = "instance-rename"
1455
  HTYPE = constants.HTYPE_INSTANCE
1456

    
1457
  def CheckArguments(self):
1458
    """Check arguments.
1459

1460
    """
1461
    if self.op.ip_check and not self.op.name_check:
1462
      # TODO: make the ip check more flexible and not depend on the name check
1463
      raise errors.OpPrereqError("IP address check requires a name check",
1464
                                 errors.ECODE_INVAL)
1465

    
1466
  def BuildHooksEnv(self):
1467
    """Build hooks env.
1468

1469
    This runs on master, primary and secondary nodes of the instance.
1470

1471
    """
1472
    env = BuildInstanceHookEnvByObject(self, self.instance)
1473
    env["INSTANCE_NEW_NAME"] = self.op.new_name
1474
    return env
1475

    
1476
  def BuildHooksNodes(self):
1477
    """Build hooks nodes.
1478

1479
    """
1480
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
1481
    return (nl, nl)
1482

    
1483
  def CheckPrereq(self):
1484
    """Check prerequisites.
1485

1486
    This checks that the instance is in the cluster and is not running.
1487

1488
    """
1489
    (self.op.instance_uuid, self.op.instance_name) = \
1490
      ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid,
1491
                                self.op.instance_name)
1492
    instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
1493
    assert instance is not None
1494

    
1495
    # It should actually not happen that an instance is running with a disabled
1496
    # disk template, but in case it does, the renaming of file-based instances
1497
    # will fail horribly. Thus, we test it before.
1498
    if (instance.disk_template in constants.DTS_FILEBASED and
1499
        self.op.new_name != instance.name):
1500
      CheckDiskTemplateEnabled(self.cfg.GetClusterInfo(),
1501
                               instance.disk_template)
1502

    
1503
    CheckNodeOnline(self, instance.primary_node)
1504
    CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
1505
                       msg="cannot rename")
1506
    self.instance = instance
1507

    
1508
    new_name = self.op.new_name
1509
    if self.op.name_check:
1510
      hostname = _CheckHostnameSane(self, new_name)
1511
      new_name = self.op.new_name = hostname.name
1512
      if (self.op.ip_check and
1513
          netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
1514
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
1515
                                   (hostname.ip, new_name),
1516
                                   errors.ECODE_NOTUNIQUE)
1517

    
1518
    instance_names = [inst.name for
1519
                      inst in self.cfg.GetAllInstancesInfo().values()]
1520
    if new_name in instance_names and new_name != instance.name:
1521
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
1522
                                 new_name, errors.ECODE_EXISTS)
1523

    
1524
  def Exec(self, feedback_fn):
1525
    """Rename the instance.
1526

1527
    """
1528
    old_name = self.instance.name
1529

    
1530
    rename_file_storage = False
1531
    if (self.instance.disk_template in constants.DTS_FILEBASED and
1532
        self.op.new_name != self.instance.name):
1533
      old_file_storage_dir = os.path.dirname(
1534
                               self.instance.disks[0].logical_id[1])
1535
      rename_file_storage = True
1536

    
1537
    self.cfg.RenameInstance(self.instance.uuid, self.op.new_name)
1538
    # Change the instance lock. This is definitely safe while we hold the BGL.
1539
    # Otherwise the new lock would have to be added in acquired mode.
1540
    assert self.REQ_BGL
1541
    assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER)
1542
    self.glm.remove(locking.LEVEL_INSTANCE, old_name)
1543
    self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
1544

    
1545
    # re-read the instance from the configuration after rename
1546
    renamed_inst = self.cfg.GetInstanceInfo(self.instance.uuid)
1547

    
1548
    if rename_file_storage:
1549
      new_file_storage_dir = os.path.dirname(
1550
                               renamed_inst.disks[0].logical_id[1])
1551
      result = self.rpc.call_file_storage_dir_rename(renamed_inst.primary_node,
1552
                                                     old_file_storage_dir,
1553
                                                     new_file_storage_dir)
1554
      result.Raise("Could not rename on node %s directory '%s' to '%s'"
1555
                   " (but the instance has been renamed in Ganeti)" %
1556
                   (self.cfg.GetNodeName(renamed_inst.primary_node),
1557
                    old_file_storage_dir, new_file_storage_dir))
1558

    
1559
    StartInstanceDisks(self, renamed_inst, None)
1560
    # update info on disks
1561
    info = GetInstanceInfoText(renamed_inst)
1562
    for (idx, disk) in enumerate(renamed_inst.disks):
1563
      for node_uuid in renamed_inst.all_nodes:
1564
        self.cfg.SetDiskID(disk, node_uuid)
1565
        result = self.rpc.call_blockdev_setinfo(node_uuid, disk, info)
1566
        result.Warn("Error setting info on node %s for disk %s" %
1567
                    (self.cfg.GetNodeName(node_uuid), idx), self.LogWarning)
1568
    try:
1569
      result = self.rpc.call_instance_run_rename(renamed_inst.primary_node,
1570
                                                 renamed_inst, old_name,
1571
                                                 self.op.debug_level)
1572
      result.Warn("Could not run OS rename script for instance %s on node %s"
1573
                  " (but the instance has been renamed in Ganeti)" %
1574
                  (renamed_inst.name,
1575
                   self.cfg.GetNodeName(renamed_inst.primary_node)),
1576
                  self.LogWarning)
1577
    finally:
1578
      ShutdownInstanceDisks(self, renamed_inst)
1579

    
1580
    return renamed_inst.name
1581

    
1582

    
1583
class LUInstanceRemove(LogicalUnit):
1584
  """Remove an instance.
1585

1586
  """
1587
  HPATH = "instance-remove"
1588
  HTYPE = constants.HTYPE_INSTANCE
1589
  REQ_BGL = False
1590

    
1591
  def ExpandNames(self):
1592
    self._ExpandAndLockInstance()
1593
    self.needed_locks[locking.LEVEL_NODE] = []
1594
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1595
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
1596

    
1597
  def DeclareLocks(self, level):
1598
    if level == locking.LEVEL_NODE:
1599
      self._LockInstancesNodes()
1600
    elif level == locking.LEVEL_NODE_RES:
1601
      # Copy node locks
1602
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1603
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1604

    
1605
  def BuildHooksEnv(self):
1606
    """Build hooks env.
1607

1608
    This runs on master, primary and secondary nodes of the instance.
1609

1610
    """
1611
    env = BuildInstanceHookEnvByObject(self, self.instance)
1612
    env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
1613
    return env
1614

    
1615
  def BuildHooksNodes(self):
1616
    """Build hooks nodes.
1617

1618
    """
1619
    nl = [self.cfg.GetMasterNode()]
1620
    nl_post = list(self.instance.all_nodes) + nl
1621
    return (nl, nl_post)
1622

    
1623
  def CheckPrereq(self):
1624
    """Check prerequisites.
1625

1626
    This checks that the instance is in the cluster.
1627

1628
    """
1629
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
1630
    assert self.instance is not None, \
1631
      "Cannot retrieve locked instance %s" % self.op.instance_name
1632

    
1633
  def Exec(self, feedback_fn):
1634
    """Remove the instance.
1635

1636
    """
1637
    logging.info("Shutting down instance %s on node %s", self.instance.name,
1638
                 self.cfg.GetNodeName(self.instance.primary_node))
1639

    
1640
    result = self.rpc.call_instance_shutdown(self.instance.primary_node,
1641
                                             self.instance,
1642
                                             self.op.shutdown_timeout,
1643
                                             self.op.reason)
1644
    if self.op.ignore_failures:
1645
      result.Warn("Warning: can't shutdown instance", feedback_fn)
1646
    else:
1647
      result.Raise("Could not shutdown instance %s on node %s" %
1648
                   (self.instance.name,
1649
                    self.cfg.GetNodeName(self.instance.primary_node)))
1650

    
1651
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1652
            self.owned_locks(locking.LEVEL_NODE_RES))
1653
    assert not (set(self.instance.all_nodes) -
1654
                self.owned_locks(locking.LEVEL_NODE)), \
1655
      "Not owning correct locks"
1656

    
1657
    RemoveInstance(self, feedback_fn, self.instance, self.op.ignore_failures)
1658

    
1659

    
1660
class LUInstanceMove(LogicalUnit):
1661
  """Move an instance by data-copying.
1662

1663
  """
1664
  HPATH = "instance-move"
1665
  HTYPE = constants.HTYPE_INSTANCE
1666
  REQ_BGL = False
1667

    
1668
  def ExpandNames(self):
1669
    self._ExpandAndLockInstance()
1670
    (self.op.target_node_uuid, self.op.target_node) = \
1671
      ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
1672
                            self.op.target_node)
1673
    self.needed_locks[locking.LEVEL_NODE] = [self.op.target_node_uuid]
1674
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1675
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
1676

    
1677
  def DeclareLocks(self, level):
1678
    if level == locking.LEVEL_NODE:
1679
      self._LockInstancesNodes(primary_only=True)
1680
    elif level == locking.LEVEL_NODE_RES:
1681
      # Copy node locks
1682
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1683
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1684

    
1685
  def BuildHooksEnv(self):
1686
    """Build hooks env.
1687

1688
    This runs on master, primary and secondary nodes of the instance.
1689

1690
    """
1691
    env = {
1692
      "TARGET_NODE": self.op.target_node,
1693
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
1694
      }
1695
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
1696
    return env
1697

    
1698
  def BuildHooksNodes(self):
1699
    """Build hooks nodes.
1700

1701
    """
1702
    nl = [
1703
      self.cfg.GetMasterNode(),
1704
      self.instance.primary_node,
1705
      self.op.target_node_uuid,
1706
      ]
1707
    return (nl, nl)
1708

    
1709
  def CheckPrereq(self):
1710
    """Check prerequisites.
1711

1712
    This checks that the instance is in the cluster.
1713

1714
    """
1715
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
1716
    assert self.instance is not None, \
1717
      "Cannot retrieve locked instance %s" % self.op.instance_name
1718

    
1719
    if self.instance.disk_template not in constants.DTS_COPYABLE:
1720
      raise errors.OpPrereqError("Disk template %s not suitable for copying" %
1721
                                 self.instance.disk_template,
1722
                                 errors.ECODE_STATE)
1723

    
1724
    target_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
1725
    assert target_node is not None, \
1726
      "Cannot retrieve locked node %s" % self.op.target_node
1727

    
1728
    self.target_node_uuid = target_node.uuid
1729
    if target_node.uuid == self.instance.primary_node:
1730
      raise errors.OpPrereqError("Instance %s is already on the node %s" %
1731
                                 (self.instance.name, target_node.name),
1732
                                 errors.ECODE_STATE)
1733

    
1734
    bep = self.cfg.GetClusterInfo().FillBE(self.instance)
1735

    
1736
    for idx, dsk in enumerate(self.instance.disks):
1737
      if dsk.dev_type not in (constants.DT_PLAIN, constants.DT_FILE,
1738
                              constants.DT_SHARED_FILE):
1739
        raise errors.OpPrereqError("Instance disk %d has a complex layout,"
1740
                                   " cannot copy" % idx, errors.ECODE_STATE)
1741

    
1742
    CheckNodeOnline(self, target_node.uuid)
1743
    CheckNodeNotDrained(self, target_node.uuid)
1744
    CheckNodeVmCapable(self, target_node.uuid)
1745
    cluster = self.cfg.GetClusterInfo()
1746
    group_info = self.cfg.GetNodeGroup(target_node.group)
1747
    ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1748
    CheckTargetNodeIPolicy(self, ipolicy, self.instance, target_node, self.cfg,
1749
                           ignore=self.op.ignore_ipolicy)
1750

    
1751
    if self.instance.admin_state == constants.ADMINST_UP:
1752
      # check memory requirements on the secondary node
1753
      CheckNodeFreeMemory(
1754
          self, target_node.uuid, "failing over instance %s" %
1755
          self.instance.name, bep[constants.BE_MAXMEM],
1756
          self.instance.hypervisor,
1757
          self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
1758
    else:
1759
      self.LogInfo("Not checking memory on the secondary node as"
1760
                   " instance will not be started")
1761

    
1762
    # check bridge existance
1763
    CheckInstanceBridgesExist(self, self.instance, node_uuid=target_node.uuid)
1764

    
1765
  def Exec(self, feedback_fn):
1766
    """Move an instance.
1767

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

1771
    """
1772
    source_node = self.cfg.GetNodeInfo(self.instance.primary_node)
1773
    target_node = self.cfg.GetNodeInfo(self.target_node_uuid)
1774

    
1775
    self.LogInfo("Shutting down instance %s on source node %s",
1776
                 self.instance.name, source_node.name)
1777

    
1778
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1779
            self.owned_locks(locking.LEVEL_NODE_RES))
1780

    
1781
    result = self.rpc.call_instance_shutdown(source_node.uuid, self.instance,
1782
                                             self.op.shutdown_timeout,
1783
                                             self.op.reason)
1784
    if self.op.ignore_consistency:
1785
      result.Warn("Could not shutdown instance %s on node %s. Proceeding"
1786
                  " anyway. Please make sure node %s is down. Error details" %
1787
                  (self.instance.name, source_node.name, source_node.name),
1788
                  self.LogWarning)
1789
    else:
1790
      result.Raise("Could not shutdown instance %s on node %s" %
1791
                   (self.instance.name, source_node.name))
1792

    
1793
    # create the target disks
1794
    try:
1795
      CreateDisks(self, self.instance, target_node_uuid=target_node.uuid)
1796
    except errors.OpExecError:
1797
      self.LogWarning("Device creation failed")
1798
      self.cfg.ReleaseDRBDMinors(self.instance.uuid)
1799
      raise
1800

    
1801
    cluster_name = self.cfg.GetClusterInfo().cluster_name
1802

    
1803
    errs = []
1804
    # activate, get path, copy the data over
1805
    for idx, disk in enumerate(self.instance.disks):
1806
      self.LogInfo("Copying data for disk %d", idx)
1807
      result = self.rpc.call_blockdev_assemble(
1808
                 target_node.uuid, (disk, self.instance), self.instance.name,
1809
                 True, idx)
1810
      if result.fail_msg:
1811
        self.LogWarning("Can't assemble newly created disk %d: %s",
1812
                        idx, result.fail_msg)
1813
        errs.append(result.fail_msg)
1814
        break
1815
      dev_path = result.payload
1816
      result = self.rpc.call_blockdev_export(source_node.uuid, (disk,
1817
                                                                self.instance),
1818
                                             target_node.name, dev_path,
1819
                                             cluster_name)
1820
      if result.fail_msg:
1821
        self.LogWarning("Can't copy data over for disk %d: %s",
1822
                        idx, result.fail_msg)
1823
        errs.append(result.fail_msg)
1824
        break
1825

    
1826
    if errs:
1827
      self.LogWarning("Some disks failed to copy, aborting")
1828
      try:
1829
        RemoveDisks(self, self.instance, target_node_uuid=target_node.uuid)
1830
      finally:
1831
        self.cfg.ReleaseDRBDMinors(self.instance.uuid)
1832
        raise errors.OpExecError("Errors during disk copy: %s" %
1833
                                 (",".join(errs),))
1834

    
1835
    self.instance.primary_node = target_node.uuid
1836
    self.cfg.Update(self.instance, feedback_fn)
1837

    
1838
    self.LogInfo("Removing the disks on the original node")
1839
    RemoveDisks(self, self.instance, target_node_uuid=source_node.uuid)
1840

    
1841
    # Only start the instance if it's marked as up
1842
    if self.instance.admin_state == constants.ADMINST_UP:
1843
      self.LogInfo("Starting instance %s on node %s",
1844
                   self.instance.name, target_node.name)
1845

    
1846
      disks_ok, _ = AssembleInstanceDisks(self, self.instance,
1847
                                          ignore_secondaries=True)
1848
      if not disks_ok:
1849
        ShutdownInstanceDisks(self, self.instance)
1850
        raise errors.OpExecError("Can't activate the instance's disks")
1851

    
1852
      result = self.rpc.call_instance_start(target_node.uuid,
1853
                                            (self.instance, None, None), False,
1854
                                            self.op.reason)
1855
      msg = result.fail_msg
1856
      if msg:
1857
        ShutdownInstanceDisks(self, self.instance)
1858
        raise errors.OpExecError("Could not start instance %s on node %s: %s" %
1859
                                 (self.instance.name, target_node.name, msg))
1860

    
1861

    
1862
class LUInstanceMultiAlloc(NoHooksLU):
1863
  """Allocates multiple instances at the same time.
1864

1865
  """
1866
  REQ_BGL = False
1867

    
1868
  def CheckArguments(self):
1869
    """Check arguments.
1870

1871
    """
1872
    nodes = []
1873
    for inst in self.op.instances:
1874
      if inst.iallocator is not None:
1875
        raise errors.OpPrereqError("iallocator are not allowed to be set on"
1876
                                   " instance objects", errors.ECODE_INVAL)
1877
      nodes.append(bool(inst.pnode))
1878
      if inst.disk_template in constants.DTS_INT_MIRROR:
1879
        nodes.append(bool(inst.snode))
1880

    
1881
    has_nodes = compat.any(nodes)
1882
    if compat.all(nodes) ^ has_nodes:
1883
      raise errors.OpPrereqError("There are instance objects providing"
1884
                                 " pnode/snode while others do not",
1885
                                 errors.ECODE_INVAL)
1886

    
1887
    if not has_nodes and self.op.iallocator is None:
1888
      default_iallocator = self.cfg.GetDefaultIAllocator()
1889
      if default_iallocator:
1890
        self.op.iallocator = default_iallocator
1891
      else:
1892
        raise errors.OpPrereqError("No iallocator or nodes on the instances"
1893
                                   " given and no cluster-wide default"
1894
                                   " iallocator found; please specify either"
1895
                                   " an iallocator or nodes on the instances"
1896
                                   " or set a cluster-wide default iallocator",
1897
                                   errors.ECODE_INVAL)
1898

    
1899
    _CheckOpportunisticLocking(self.op)
1900

    
1901
    dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
1902
    if dups:
1903
      raise errors.OpPrereqError("There are duplicate instance names: %s" %
1904
                                 utils.CommaJoin(dups), errors.ECODE_INVAL)
1905

    
1906
  def ExpandNames(self):
1907
    """Calculate the locks.
1908

1909
    """
1910
    self.share_locks = ShareAll()
1911
    self.needed_locks = {
1912
      # iallocator will select nodes and even if no iallocator is used,
1913
      # collisions with LUInstanceCreate should be avoided
1914
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
1915
      }
1916

    
1917
    if self.op.iallocator:
1918
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
1919
      self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
1920

    
1921
      if self.op.opportunistic_locking:
1922
        self.opportunistic_locks[locking.LEVEL_NODE] = True
1923
        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
1924
    else:
1925
      nodeslist = []
1926
      for inst in self.op.instances:
1927
        (inst.pnode_uuid, inst.pnode) = \
1928
          ExpandNodeUuidAndName(self.cfg, inst.pnode_uuid, inst.pnode)
1929
        nodeslist.append(inst.pnode_uuid)
1930
        if inst.snode is not None:
1931
          (inst.snode_uuid, inst.snode) = \
1932
            ExpandNodeUuidAndName(self.cfg, inst.snode_uuid, inst.snode)
1933
          nodeslist.append(inst.snode_uuid)
1934

    
1935
      self.needed_locks[locking.LEVEL_NODE] = nodeslist
1936
      # Lock resources of instance's primary and secondary nodes (copy to
1937
      # prevent accidential modification)
1938
      self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
1939

    
1940
  def CheckPrereq(self):
1941
    """Check prerequisite.
1942

1943
    """
1944
    if self.op.iallocator:
1945
      cluster = self.cfg.GetClusterInfo()
1946
      default_vg = self.cfg.GetVGName()
1947
      ec_id = self.proc.GetECId()
1948

    
1949
      if self.op.opportunistic_locking:
1950
        # Only consider nodes for which a lock is held
1951
        node_whitelist = self.cfg.GetNodeNames(
1952
                           list(self.owned_locks(locking.LEVEL_NODE)))
1953
      else:
1954
        node_whitelist = None
1955

    
1956
      insts = [_CreateInstanceAllocRequest(op, ComputeDisks(op, default_vg),
1957
                                           _ComputeNics(op, cluster, None,
1958
                                                        self.cfg, ec_id),
1959
                                           _ComputeFullBeParams(op, cluster),
1960
                                           node_whitelist)
1961
               for op in self.op.instances]
1962

    
1963
      req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
1964
      ial = iallocator.IAllocator(self.cfg, self.rpc, req)
1965

    
1966
      ial.Run(self.op.iallocator)
1967

    
1968
      if not ial.success:
1969
        raise errors.OpPrereqError("Can't compute nodes using"
1970
                                   " iallocator '%s': %s" %
1971
                                   (self.op.iallocator, ial.info),
1972
                                   errors.ECODE_NORES)
1973

    
1974
      self.ia_result = ial.result
1975

    
1976
    if self.op.dry_run:
1977
      self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
1978
        constants.JOB_IDS_KEY: [],
1979
        })
1980

    
1981
  def _ConstructPartialResult(self):
1982
    """Contructs the partial result.
1983

1984
    """
1985
    if self.op.iallocator:
1986
      (allocatable, failed_insts) = self.ia_result
1987
      allocatable_insts = map(compat.fst, allocatable)
1988
    else:
1989
      allocatable_insts = [op.instance_name for op in self.op.instances]
1990
      failed_insts = []
1991

    
1992
    return {
1993
      constants.ALLOCATABLE_KEY: allocatable_insts,
1994
      constants.FAILED_KEY: failed_insts,
1995
      }
1996

    
1997
  def Exec(self, feedback_fn):
1998
    """Executes the opcode.
1999

2000
    """
2001
    jobs = []
2002
    if self.op.iallocator:
2003
      op2inst = dict((op.instance_name, op) for op in self.op.instances)
2004
      (allocatable, failed) = self.ia_result
2005

    
2006
      for (name, node_names) in allocatable:
2007
        op = op2inst.pop(name)
2008

    
2009
        (op.pnode_uuid, op.pnode) = \
2010
          ExpandNodeUuidAndName(self.cfg, None, node_names[0])
2011
        if len(node_names) > 1:
2012
          (op.snode_uuid, op.snode) = \
2013
            ExpandNodeUuidAndName(self.cfg, None, node_names[1])
2014

    
2015
          jobs.append([op])
2016

    
2017
        missing = set(op2inst.keys()) - set(failed)
2018
        assert not missing, \
2019
          "Iallocator did return incomplete result: %s" % \
2020
          utils.CommaJoin(missing)
2021
    else:
2022
      jobs.extend([op] for op in self.op.instances)
2023

    
2024
    return ResultWithJobs(jobs, **self._ConstructPartialResult())
2025

    
2026

    
2027
class _InstNicModPrivate:
2028
  """Data structure for network interface modifications.
2029

2030
  Used by L{LUInstanceSetParams}.
2031

2032
  """
2033
  def __init__(self):
2034
    self.params = None
2035
    self.filled = None
2036

    
2037

    
2038
def _PrepareContainerMods(mods, private_fn):
2039
  """Prepares a list of container modifications by adding a private data field.
2040

2041
  @type mods: list of tuples; (operation, index, parameters)
2042
  @param mods: List of modifications
2043
  @type private_fn: callable or None
2044
  @param private_fn: Callable for constructing a private data field for a
2045
    modification
2046
  @rtype: list
2047

2048
  """
2049
  if private_fn is None:
2050
    fn = lambda: None
2051
  else:
2052
    fn = private_fn
2053

    
2054
  return [(op, idx, params, fn()) for (op, idx, params) in mods]
2055

    
2056

    
2057
def _CheckNodesPhysicalCPUs(lu, node_uuids, requested, hypervisor_specs):
2058
  """Checks if nodes have enough physical CPUs
2059

2060
  This function checks if all given nodes have the needed number of
2061
  physical CPUs. In case any node has less CPUs or we cannot get the
2062
  information from the node, this function raises an OpPrereqError
2063
  exception.
2064

2065
  @type lu: C{LogicalUnit}
2066
  @param lu: a logical unit from which we get configuration data
2067
  @type node_uuids: C{list}
2068
  @param node_uuids: the list of node UUIDs to check
2069
  @type requested: C{int}
2070
  @param requested: the minimum acceptable number of physical CPUs
2071
  @type hypervisor_specs: list of pairs (string, dict of strings)
2072
  @param hypervisor_specs: list of hypervisor specifications in
2073
      pairs (hypervisor_name, hvparams)
2074
  @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
2075
      or we cannot check the node
2076

2077
  """
2078
  nodeinfo = lu.rpc.call_node_info(node_uuids, None, hypervisor_specs)
2079
  for node_uuid in node_uuids:
2080
    info = nodeinfo[node_uuid]
2081
    node_name = lu.cfg.GetNodeName(node_uuid)
2082
    info.Raise("Cannot get current information from node %s" % node_name,
2083
               prereq=True, ecode=errors.ECODE_ENVIRON)
2084
    (_, _, (hv_info, )) = info.payload
2085
    num_cpus = hv_info.get("cpu_total", None)
2086
    if not isinstance(num_cpus, int):
2087
      raise errors.OpPrereqError("Can't compute the number of physical CPUs"
2088
                                 " on node %s, result was '%s'" %
2089
                                 (node_name, num_cpus), errors.ECODE_ENVIRON)
2090
    if requested > num_cpus:
2091
      raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
2092
                                 "required" % (node_name, num_cpus, requested),
2093
                                 errors.ECODE_NORES)
2094

    
2095

    
2096
def GetItemFromContainer(identifier, kind, container):
2097
  """Return the item refered by the identifier.
2098

2099
  @type identifier: string
2100
  @param identifier: Item index or name or UUID
2101
  @type kind: string
2102
  @param kind: One-word item description
2103
  @type container: list
2104
  @param container: Container to get the item from
2105

2106
  """
2107
  # Index
2108
  try:
2109
    idx = int(identifier)
2110
    if idx == -1:
2111
      # Append
2112
      absidx = len(container) - 1
2113
    elif idx < 0:
2114
      raise IndexError("Not accepting negative indices other than -1")
2115
    elif idx > len(container):
2116
      raise IndexError("Got %s index %s, but there are only %s" %
2117
                       (kind, idx, len(container)))
2118
    else:
2119
      absidx = idx
2120
    return (absidx, container[idx])
2121
  except ValueError:
2122
    pass
2123

    
2124
  for idx, item in enumerate(container):
2125
    if item.uuid == identifier or item.name == identifier:
2126
      return (idx, item)
2127

    
2128
  raise errors.OpPrereqError("Cannot find %s with identifier %s" %
2129
                             (kind, identifier), errors.ECODE_NOENT)
2130

    
2131

    
2132
def _ApplyContainerMods(kind, container, chgdesc, mods,
2133
                        create_fn, modify_fn, remove_fn):
2134
  """Applies descriptions in C{mods} to C{container}.
2135

2136
  @type kind: string
2137
  @param kind: One-word item description
2138
  @type container: list
2139
  @param container: Container to modify
2140
  @type chgdesc: None or list
2141
  @param chgdesc: List of applied changes
2142
  @type mods: list
2143
  @param mods: Modifications as returned by L{_PrepareContainerMods}
2144
  @type create_fn: callable
2145
  @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
2146
    receives absolute item index, parameters and private data object as added
2147
    by L{_PrepareContainerMods}, returns tuple containing new item and changes
2148
    as list
2149
  @type modify_fn: callable
2150
  @param modify_fn: Callback for modifying an existing item
2151
    (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
2152
    and private data object as added by L{_PrepareContainerMods}, returns
2153
    changes as list
2154
  @type remove_fn: callable
2155
  @param remove_fn: Callback on removing item; receives absolute item index,
2156
    item and private data object as added by L{_PrepareContainerMods}
2157

2158
  """
2159
  for (op, identifier, params, private) in mods:
2160
    changes = None
2161

    
2162
    if op == constants.DDM_ADD:
2163
      # Calculate where item will be added
2164
      # When adding an item, identifier can only be an index
2165
      try:
2166
        idx = int(identifier)
2167
      except ValueError:
2168
        raise errors.OpPrereqError("Only possitive integer or -1 is accepted as"
2169
                                   " identifier for %s" % constants.DDM_ADD,
2170
                                   errors.ECODE_INVAL)
2171
      if idx == -1:
2172
        addidx = len(container)
2173
      else:
2174
        if idx < 0:
2175
          raise IndexError("Not accepting negative indices other than -1")
2176
        elif idx > len(container):
2177
          raise IndexError("Got %s index %s, but there are only %s" %
2178
                           (kind, idx, len(container)))
2179
        addidx = idx
2180

    
2181
      if create_fn is None:
2182
        item = params
2183
      else:
2184
        (item, changes) = create_fn(addidx, params, private)
2185

    
2186
      if idx == -1:
2187
        container.append(item)
2188
      else:
2189
        assert idx >= 0
2190
        assert idx <= len(container)
2191
        # list.insert does so before the specified index
2192
        container.insert(idx, item)
2193
    else:
2194
      # Retrieve existing item
2195
      (absidx, item) = GetItemFromContainer(identifier, kind, container)
2196

    
2197
      if op == constants.DDM_REMOVE:
2198
        assert not params
2199

    
2200
        if remove_fn is not None:
2201
          remove_fn(absidx, item, private)
2202

    
2203
        changes = [("%s/%s" % (kind, absidx), "remove")]
2204

    
2205
        assert container[absidx] == item
2206
        del container[absidx]
2207
      elif op == constants.DDM_MODIFY:
2208
        if modify_fn is not None:
2209
          changes = modify_fn(absidx, item, params, private)
2210
      else:
2211
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2212

    
2213
    assert _TApplyContModsCbChanges(changes)
2214

    
2215
    if not (chgdesc is None or changes is None):
2216
      chgdesc.extend(changes)
2217

    
2218

    
2219
def _UpdateIvNames(base_index, disks):
2220
  """Updates the C{iv_name} attribute of disks.
2221

2222
  @type disks: list of L{objects.Disk}
2223

2224
  """
2225
  for (idx, disk) in enumerate(disks):
2226
    disk.iv_name = "disk/%s" % (base_index + idx, )
2227

    
2228

    
2229
class LUInstanceSetParams(LogicalUnit):
2230
  """Modifies an instances's parameters.
2231

2232
  """
2233
  HPATH = "instance-modify"
2234
  HTYPE = constants.HTYPE_INSTANCE
2235
  REQ_BGL = False
2236

    
2237
  @staticmethod
2238
  def _UpgradeDiskNicMods(kind, mods, verify_fn):
2239
    assert ht.TList(mods)
2240
    assert not mods or len(mods[0]) in (2, 3)
2241

    
2242
    if mods and len(mods[0]) == 2:
2243
      result = []
2244

    
2245
      addremove = 0
2246
      for op, params in mods:
2247
        if op in (constants.DDM_ADD, constants.DDM_REMOVE):
2248
          result.append((op, -1, params))
2249
          addremove += 1
2250

    
2251
          if addremove > 1:
2252
            raise errors.OpPrereqError("Only one %s add or remove operation is"
2253
                                       " supported at a time" % kind,
2254
                                       errors.ECODE_INVAL)
2255
        else:
2256
          result.append((constants.DDM_MODIFY, op, params))
2257

    
2258
      assert verify_fn(result)
2259
    else:
2260
      result = mods
2261

    
2262
    return result
2263

    
2264
  @staticmethod
2265
  def _CheckMods(kind, mods, key_types, item_fn):
2266
    """Ensures requested disk/NIC modifications are valid.
2267

2268
    """
2269
    for (op, _, params) in mods:
2270
      assert ht.TDict(params)
2271

    
2272
      # If 'key_types' is an empty dict, we assume we have an
2273
      # 'ext' template and thus do not ForceDictType
2274
      if key_types:
2275
        utils.ForceDictType(params, key_types)
2276

    
2277
      if op == constants.DDM_REMOVE:
2278
        if params:
2279
          raise errors.OpPrereqError("No settings should be passed when"
2280
                                     " removing a %s" % kind,
2281
                                     errors.ECODE_INVAL)
2282
      elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
2283
        item_fn(op, params)
2284
      else:
2285
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2286

    
2287
  @staticmethod
2288
  def _VerifyDiskModification(op, params, excl_stor):
2289
    """Verifies a disk modification.
2290

2291
    """
2292
    if op == constants.DDM_ADD:
2293
      mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
2294
      if mode not in constants.DISK_ACCESS_SET:
2295
        raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
2296
                                   errors.ECODE_INVAL)
2297

    
2298
      size = params.get(constants.IDISK_SIZE, None)
2299
      if size is None:
2300
        raise errors.OpPrereqError("Required disk parameter '%s' missing" %
2301
                                   constants.IDISK_SIZE, errors.ECODE_INVAL)
2302
      size = int(size)
2303

    
2304
      params[constants.IDISK_SIZE] = size
2305
      name = params.get(constants.IDISK_NAME, None)
2306
      if name is not None and name.lower() == constants.VALUE_NONE:
2307
        params[constants.IDISK_NAME] = None
2308

    
2309
      CheckSpindlesExclusiveStorage(params, excl_stor, True)
2310

    
2311
    elif op == constants.DDM_MODIFY:
2312
      if constants.IDISK_SIZE in params:
2313
        raise errors.OpPrereqError("Disk size change not possible, use"
2314
                                   " grow-disk", errors.ECODE_INVAL)
2315
      if len(params) > 2:
2316
        raise errors.OpPrereqError("Disk modification doesn't support"
2317
                                   " additional arbitrary parameters",
2318
                                   errors.ECODE_INVAL)
2319
      name = params.get(constants.IDISK_NAME, None)
2320
      if name is not None and name.lower() == constants.VALUE_NONE:
2321
        params[constants.IDISK_NAME] = None
2322

    
2323
  @staticmethod
2324
  def _VerifyNicModification(op, params):
2325
    """Verifies a network interface modification.
2326

2327
    """
2328
    if op in (constants.DDM_ADD, constants.DDM_MODIFY):
2329
      ip = params.get(constants.INIC_IP, None)
2330
      name = params.get(constants.INIC_NAME, None)
2331
      req_net = params.get(constants.INIC_NETWORK, None)
2332
      link = params.get(constants.NIC_LINK, None)
2333
      mode = params.get(constants.NIC_MODE, None)
2334
      if name is not None and name.lower() == constants.VALUE_NONE:
2335
        params[constants.INIC_NAME] = None
2336
      if req_net is not None:
2337
        if req_net.lower() == constants.VALUE_NONE:
2338
          params[constants.INIC_NETWORK] = None
2339
          req_net = None
2340
        elif link is not None or mode is not None:
2341
          raise errors.OpPrereqError("If network is given"
2342
                                     " mode or link should not",
2343
                                     errors.ECODE_INVAL)
2344

    
2345
      if op == constants.DDM_ADD:
2346
        macaddr = params.get(constants.INIC_MAC, None)
2347
        if macaddr is None:
2348
          params[constants.INIC_MAC] = constants.VALUE_AUTO
2349

    
2350
      if ip is not None:
2351
        if ip.lower() == constants.VALUE_NONE:
2352
          params[constants.INIC_IP] = None
2353
        else:
2354
          if ip.lower() == constants.NIC_IP_POOL:
2355
            if op == constants.DDM_ADD and req_net is None:
2356
              raise errors.OpPrereqError("If ip=pool, parameter network"
2357
                                         " cannot be none",
2358
                                         errors.ECODE_INVAL)
2359
          else:
2360
            if not netutils.IPAddress.IsValid(ip):
2361
              raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
2362
                                         errors.ECODE_INVAL)
2363

    
2364
      if constants.INIC_MAC in params:
2365
        macaddr = params[constants.INIC_MAC]
2366
        if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2367
          macaddr = utils.NormalizeAndValidateMac(macaddr)
2368

    
2369
        if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
2370
          raise errors.OpPrereqError("'auto' is not a valid MAC address when"
2371
                                     " modifying an existing NIC",
2372
                                     errors.ECODE_INVAL)
2373

    
2374
  def CheckArguments(self):
2375
    if not (self.op.nics or self.op.disks or self.op.disk_template or
2376
            self.op.hvparams or self.op.beparams or self.op.os_name or
2377
            self.op.osparams or self.op.offline is not None or
2378
            self.op.runtime_mem or self.op.pnode):
2379
      raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
2380

    
2381
    if self.op.hvparams:
2382
      CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
2383
                           "hypervisor", "instance", "cluster")
2384

    
2385
    self.op.disks = self._UpgradeDiskNicMods(
2386
      "disk", self.op.disks, ht.TSetParamsMods(ht.TIDiskParams))
2387
    self.op.nics = self._UpgradeDiskNicMods(
2388
      "NIC", self.op.nics, ht.TSetParamsMods(ht.TINicParams))
2389

    
2390
    if self.op.disks and self.op.disk_template is not None:
2391
      raise errors.OpPrereqError("Disk template conversion and other disk"
2392
                                 " changes not supported at the same time",
2393
                                 errors.ECODE_INVAL)
2394

    
2395
    if (self.op.disk_template and
2396
        self.op.disk_template in constants.DTS_INT_MIRROR and
2397
        self.op.remote_node is None):
2398
      raise errors.OpPrereqError("Changing the disk template to a mirrored"
2399
                                 " one requires specifying a secondary node",
2400
                                 errors.ECODE_INVAL)
2401

    
2402
    # Check NIC modifications
2403
    self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
2404
                    self._VerifyNicModification)
2405

    
2406
    if self.op.pnode:
2407
      (self.op.pnode_uuid, self.op.pnode) = \
2408
        ExpandNodeUuidAndName(self.cfg, self.op.pnode_uuid, self.op.pnode)
2409

    
2410
  def ExpandNames(self):
2411
    self._ExpandAndLockInstance()
2412
    self.needed_locks[locking.LEVEL_NODEGROUP] = []
2413
    # Can't even acquire node locks in shared mode as upcoming changes in
2414
    # Ganeti 2.6 will start to modify the node object on disk conversion
2415
    self.needed_locks[locking.LEVEL_NODE] = []
2416
    self.needed_locks[locking.LEVEL_NODE_RES] = []
2417
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2418
    # Look node group to look up the ipolicy
2419
    self.share_locks[locking.LEVEL_NODEGROUP] = 1
2420

    
2421
  def DeclareLocks(self, level):
2422
    if level == locking.LEVEL_NODEGROUP:
2423
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
2424
      # Acquire locks for the instance's nodegroups optimistically. Needs
2425
      # to be verified in CheckPrereq
2426
      self.needed_locks[locking.LEVEL_NODEGROUP] = \
2427
        self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
2428
    elif level == locking.LEVEL_NODE:
2429
      self._LockInstancesNodes()
2430
      if self.op.disk_template and self.op.remote_node:
2431
        (self.op.remote_node_uuid, self.op.remote_node) = \
2432
          ExpandNodeUuidAndName(self.cfg, self.op.remote_node_uuid,
2433
                                self.op.remote_node)
2434
        self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node_uuid)
2435
    elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
2436
      # Copy node locks
2437
      self.needed_locks[locking.LEVEL_NODE_RES] = \
2438
        CopyLockList(self.needed_locks[locking.LEVEL_NODE])
2439

    
2440
  def BuildHooksEnv(self):
2441
    """Build hooks env.
2442

2443
    This runs on the master, primary and secondaries.
2444

2445
    """
2446
    args = {}
2447
    if constants.BE_MINMEM in self.be_new:
2448
      args["minmem"] = self.be_new[constants.BE_MINMEM]
2449
    if constants.BE_MAXMEM in self.be_new:
2450
      args["maxmem"] = self.be_new[constants.BE_MAXMEM]
2451
    if constants.BE_VCPUS in self.be_new:
2452
      args["vcpus"] = self.be_new[constants.BE_VCPUS]
2453
    # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
2454
    # information at all.
2455

    
2456
    if self._new_nics is not None:
2457
      nics = []
2458

    
2459
      for nic in self._new_nics:
2460
        n = copy.deepcopy(nic)
2461
        nicparams = self.cluster.SimpleFillNIC(n.nicparams)
2462
        n.nicparams = nicparams
2463
        nics.append(NICToTuple(self, n))
2464

    
2465
      args["nics"] = nics
2466

    
2467
    env = BuildInstanceHookEnvByObject(self, self.instance, override=args)
2468
    if self.op.disk_template:
2469
      env["NEW_DISK_TEMPLATE"] = self.op.disk_template
2470
    if self.op.runtime_mem:
2471
      env["RUNTIME_MEMORY"] = self.op.runtime_mem
2472

    
2473
    return env
2474

    
2475
  def BuildHooksNodes(self):
2476
    """Build hooks nodes.
2477

2478
    """
2479
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
2480
    return (nl, nl)
2481

    
2482
  def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
2483
                              old_params, cluster, pnode_uuid):
2484

    
2485
    update_params_dict = dict([(key, params[key])
2486
                               for key in constants.NICS_PARAMETERS
2487
                               if key in params])
2488

    
2489
    req_link = update_params_dict.get(constants.NIC_LINK, None)
2490
    req_mode = update_params_dict.get(constants.NIC_MODE, None)
2491

    
2492
    new_net_uuid = None
2493
    new_net_uuid_or_name = params.get(constants.INIC_NETWORK, old_net_uuid)
2494
    if new_net_uuid_or_name:
2495
      new_net_uuid = self.cfg.LookupNetwork(new_net_uuid_or_name)
2496
      new_net_obj = self.cfg.GetNetwork(new_net_uuid)
2497

    
2498
    if old_net_uuid:
2499
      old_net_obj = self.cfg.GetNetwork(old_net_uuid)
2500

    
2501
    if new_net_uuid:
2502
      netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode_uuid)
2503
      if not netparams:
2504
        raise errors.OpPrereqError("No netparams found for the network"
2505
                                   " %s, probably not connected" %
2506
                                   new_net_obj.name, errors.ECODE_INVAL)
2507
      new_params = dict(netparams)
2508
    else:
2509
      new_params = GetUpdatedParams(old_params, update_params_dict)
2510

    
2511
    utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
2512

    
2513
    new_filled_params = cluster.SimpleFillNIC(new_params)
2514
    objects.NIC.CheckParameterSyntax(new_filled_params)
2515

    
2516
    new_mode = new_filled_params[constants.NIC_MODE]
2517
    if new_mode == constants.NIC_MODE_BRIDGED:
2518
      bridge = new_filled_params[constants.NIC_LINK]
2519
      msg = self.rpc.call_bridges_exist(pnode_uuid, [bridge]).fail_msg
2520
      if msg:
2521
        msg = "Error checking bridges on node '%s': %s" % \
2522
                (self.cfg.GetNodeName(pnode_uuid), msg)
2523
        if self.op.force:
2524
          self.warn.append(msg)
2525
        else:
2526
          raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
2527

    
2528
    elif new_mode == constants.NIC_MODE_ROUTED:
2529
      ip = params.get(constants.INIC_IP, old_ip)
2530
      if ip is None:
2531
        raise errors.OpPrereqError("Cannot set the NIC IP address to None"
2532
                                   " on a routed NIC", errors.ECODE_INVAL)
2533

    
2534
    elif new_mode == constants.NIC_MODE_OVS:
2535
      # TODO: check OVS link
2536
      self.LogInfo("OVS links are currently not checked for correctness")
2537

    
2538
    if constants.INIC_MAC in params:
2539
      mac = params[constants.INIC_MAC]
2540
      if mac is None:
2541
        raise errors.OpPrereqError("Cannot unset the NIC MAC address",
2542
                                   errors.ECODE_INVAL)
2543
      elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2544
        # otherwise generate the MAC address
2545
        params[constants.INIC_MAC] = \
2546
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2547
      else:
2548
        # or validate/reserve the current one
2549
        try:
2550
          self.cfg.ReserveMAC(mac, self.proc.GetECId())
2551
        except errors.ReservationError:
2552
          raise errors.OpPrereqError("MAC address '%s' already in use"
2553
                                     " in cluster" % mac,
2554
                                     errors.ECODE_NOTUNIQUE)
2555
    elif new_net_uuid != old_net_uuid:
2556

    
2557
      def get_net_prefix(net_uuid):
2558
        mac_prefix = None
2559
        if net_uuid:
2560
          nobj = self.cfg.GetNetwork(net_uuid)
2561
          mac_prefix = nobj.mac_prefix
2562

    
2563
        return mac_prefix
2564

    
2565
      new_prefix = get_net_prefix(new_net_uuid)
2566
      old_prefix = get_net_prefix(old_net_uuid)
2567
      if old_prefix != new_prefix:
2568
        params[constants.INIC_MAC] = \
2569
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2570

    
2571
    # if there is a change in (ip, network) tuple
2572
    new_ip = params.get(constants.INIC_IP, old_ip)
2573
    if (new_ip, new_net_uuid) != (old_ip, old_net_uuid):
2574
      if new_ip:
2575
        # if IP is pool then require a network and generate one IP
2576
        if new_ip.lower() == constants.NIC_IP_POOL:
2577
          if new_net_uuid:
2578
            try:
2579
              new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId())
2580
            except errors.ReservationError:
2581
              raise errors.OpPrereqError("Unable to get a free IP"
2582
                                         " from the address pool",
2583
                                         errors.ECODE_STATE)
2584
            self.LogInfo("Chose IP %s from network %s",
2585
                         new_ip,
2586
                         new_net_obj.name)
2587
            params[constants.INIC_IP] = new_ip
2588
          else:
2589
            raise errors.OpPrereqError("ip=pool, but no network found",
2590
                                       errors.ECODE_INVAL)
2591
        # Reserve new IP if in the new network if any
2592
        elif new_net_uuid:
2593
          try:
2594
            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId())
2595
            self.LogInfo("Reserving IP %s in network %s",
2596
                         new_ip, new_net_obj.name)
2597
          except errors.ReservationError:
2598
            raise errors.OpPrereqError("IP %s not available in network %s" %
2599
                                       (new_ip, new_net_obj.name),
2600
                                       errors.ECODE_NOTUNIQUE)
2601
        # new network is None so check if new IP is a conflicting IP
2602
        elif self.op.conflicts_check:
2603
          _CheckForConflictingIp(self, new_ip, pnode_uuid)
2604

    
2605
      # release old IP if old network is not None
2606
      if old_ip and old_net_uuid:
2607
        try:
2608
          self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId())
2609
        except errors.AddressPoolError:
2610
          logging.warning("Release IP %s not contained in network %s",
2611
                          old_ip, old_net_obj.name)
2612

    
2613
    # there are no changes in (ip, network) tuple and old network is not None
2614
    elif (old_net_uuid is not None and
2615
          (req_link is not None or req_mode is not None)):
2616
      raise errors.OpPrereqError("Not allowed to change link or mode of"
2617
                                 " a NIC that is connected to a network",
2618
                                 errors.ECODE_INVAL)
2619

    
2620
    private.params = new_params
2621
    private.filled = new_filled_params
2622

    
2623
  def _PreCheckDiskTemplate(self, pnode_info):
2624
    """CheckPrereq checks related to a new disk template."""
2625
    # Arguments are passed to avoid configuration lookups
2626
    pnode_uuid = self.instance.primary_node
2627
    if self.instance.disk_template == self.op.disk_template:
2628
      raise errors.OpPrereqError("Instance already has disk template %s" %
2629
                                 self.instance.disk_template,
2630
                                 errors.ECODE_INVAL)
2631

    
2632
    if not self.cluster.IsDiskTemplateEnabled(self.op.disk_template):
2633
      raise errors.OpPrereqError("Disk template '%s' is not enabled for this"
2634
                                 " cluster." % self.op.disk_template)
2635

    
2636
    if (self.instance.disk_template,
2637
        self.op.disk_template) not in self._DISK_CONVERSIONS:
2638
      raise errors.OpPrereqError("Unsupported disk template conversion from"
2639
                                 " %s to %s" % (self.instance.disk_template,
2640
                                                self.op.disk_template),
2641
                                 errors.ECODE_INVAL)
2642
    CheckInstanceState(self, self.instance, INSTANCE_DOWN,
2643
                       msg="cannot change disk template")
2644
    if self.op.disk_template in constants.DTS_INT_MIRROR:
2645
      if self.op.remote_node_uuid == pnode_uuid:
2646
        raise errors.OpPrereqError("Given new secondary node %s is the same"
2647
                                   " as the primary node of the instance" %
2648
                                   self.op.remote_node, errors.ECODE_STATE)
2649
      CheckNodeOnline(self, self.op.remote_node_uuid)
2650
      CheckNodeNotDrained(self, self.op.remote_node_uuid)
2651
      # FIXME: here we assume that the old instance type is DT_PLAIN
2652
      assert self.instance.disk_template == constants.DT_PLAIN
2653
      disks = [{constants.IDISK_SIZE: d.size,
2654
                constants.IDISK_VG: d.logical_id[0]}
2655
               for d in self.instance.disks]
2656
      required = ComputeDiskSizePerVG(self.op.disk_template, disks)
2657
      CheckNodesFreeDiskPerVG(self, [self.op.remote_node_uuid], required)
2658

    
2659
      snode_info = self.cfg.GetNodeInfo(self.op.remote_node_uuid)
2660
      snode_group = self.cfg.GetNodeGroup(snode_info.group)
2661
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(self.cluster,
2662
                                                              snode_group)
2663
      CheckTargetNodeIPolicy(self, ipolicy, self.instance, snode_info, self.cfg,
2664
                             ignore=self.op.ignore_ipolicy)
2665
      if pnode_info.group != snode_info.group:
2666
        self.LogWarning("The primary and secondary nodes are in two"
2667
                        " different node groups; the disk parameters"
2668
                        " from the first disk's node group will be"
2669
                        " used")
2670

    
2671
    if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
2672
      # Make sure none of the nodes require exclusive storage
2673
      nodes = [pnode_info]
2674
      if self.op.disk_template in constants.DTS_INT_MIRROR:
2675
        assert snode_info
2676
        nodes.append(snode_info)
2677
      has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
2678
      if compat.any(map(has_es, nodes)):
2679
        errmsg = ("Cannot convert disk template from %s to %s when exclusive"
2680
                  " storage is enabled" % (self.instance.disk_template,
2681
                                           self.op.disk_template))
2682
        raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
2683

    
2684
  def _PreCheckDisks(self, ispec):
2685
    """CheckPrereq checks related to disk changes.
2686

2687
    @type ispec: dict
2688
    @param ispec: instance specs to be updated with the new disks
2689

2690
    """
2691
    self.diskparams = self.cfg.GetInstanceDiskParams(self.instance)
2692

    
2693
    excl_stor = compat.any(
2694
      rpc.GetExclusiveStorageForNodes(self.cfg,
2695
                                      self.instance.all_nodes).values()
2696
      )
2697

    
2698
    # Check disk modifications. This is done here and not in CheckArguments
2699
    # (as with NICs), because we need to know the instance's disk template
2700
    ver_fn = lambda op, par: self._VerifyDiskModification(op, par, excl_stor)
2701
    if self.instance.disk_template == constants.DT_EXT:
2702
      self._CheckMods("disk", self.op.disks, {}, ver_fn)
2703
    else:
2704
      self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
2705
                      ver_fn)
2706

    
2707
    self.diskmod = _PrepareContainerMods(self.op.disks, None)
2708

    
2709
    # Check the validity of the `provider' parameter
2710
    if self.instance.disk_template in constants.DT_EXT:
2711
      for mod in self.diskmod:
2712
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2713
        if mod[0] == constants.DDM_ADD:
2714
          if ext_provider is None:
2715
            raise errors.OpPrereqError("Instance template is '%s' and parameter"
2716
                                       " '%s' missing, during disk add" %
2717
                                       (constants.DT_EXT,
2718
                                        constants.IDISK_PROVIDER),
2719
                                       errors.ECODE_NOENT)
2720
        elif mod[0] == constants.DDM_MODIFY:
2721
          if ext_provider:
2722
            raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
2723
                                       " modification" %
2724
                                       constants.IDISK_PROVIDER,
2725
                                       errors.ECODE_INVAL)
2726
    else:
2727
      for mod in self.diskmod:
2728
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2729
        if ext_provider is not None:
2730
          raise errors.OpPrereqError("Parameter '%s' is only valid for"
2731
                                     " instances of type '%s'" %
2732
                                     (constants.IDISK_PROVIDER,
2733
                                      constants.DT_EXT),
2734
                                     errors.ECODE_INVAL)
2735

    
2736
    if self.op.disks and self.instance.disk_template == constants.DT_DISKLESS:
2737
      raise errors.OpPrereqError("Disk operations not supported for"
2738
                                 " diskless instances", errors.ECODE_INVAL)
2739

    
2740
    def _PrepareDiskMod(_, disk, params, __):
2741
      disk.name = params.get(constants.IDISK_NAME, None)
2742

    
2743
    # Verify disk changes (operating on a copy)
2744
    disks = copy.deepcopy(self.instance.disks)
2745
    _ApplyContainerMods("disk", disks, None, self.diskmod, None,
2746
                        _PrepareDiskMod, None)
2747
    utils.ValidateDeviceNames("disk", disks)
2748
    if len(disks) > constants.MAX_DISKS:
2749
      raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
2750
                                 " more" % constants.MAX_DISKS,
2751
                                 errors.ECODE_STATE)
2752
    disk_sizes = [disk.size for disk in self.instance.disks]
2753
    disk_sizes.extend(params["size"] for (op, idx, params, private) in
2754
                      self.diskmod if op == constants.DDM_ADD)
2755
    ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
2756
    ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
2757

    
2758
    if self.op.offline is not None and self.op.offline:
2759
      CheckInstanceState(self, self.instance, CAN_CHANGE_INSTANCE_OFFLINE,
2760
                         msg="can't change to offline")
2761

    
2762
  def CheckPrereq(self):
2763
    """Check prerequisites.
2764

2765
    This only checks the instance list against the existing names.
2766

2767
    """
2768
    assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
2769
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
2770
    self.cluster = self.cfg.GetClusterInfo()
2771

    
2772
    assert self.instance is not None, \
2773
      "Cannot retrieve locked instance %s" % self.op.instance_name
2774

    
2775
    pnode_uuid = self.instance.primary_node
2776

    
2777
    self.warn = []
2778

    
2779
    if (self.op.pnode_uuid is not None and self.op.pnode_uuid != pnode_uuid and
2780
        not self.op.force):
2781
      # verify that the instance is not up
2782
      instance_info = self.rpc.call_instance_info(
2783
          pnode_uuid, self.instance.name, self.instance.hypervisor,
2784
          self.instance.hvparams)
2785
      if instance_info.fail_msg:
2786
        self.warn.append("Can't get instance runtime information: %s" %
2787
                         instance_info.fail_msg)
2788
      elif instance_info.payload:
2789
        raise errors.OpPrereqError("Instance is still running on %s" %
2790
                                   self.cfg.GetNodeName(pnode_uuid),
2791
                                   errors.ECODE_STATE)
2792

    
2793
    assert pnode_uuid in self.owned_locks(locking.LEVEL_NODE)
2794
    node_uuids = list(self.instance.all_nodes)
2795
    pnode_info = self.cfg.GetNodeInfo(pnode_uuid)
2796

    
2797
    #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
2798
    assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
2799
    group_info = self.cfg.GetNodeGroup(pnode_info.group)
2800

    
2801
    # dictionary with instance information after the modification
2802
    ispec = {}
2803

    
2804
    # Prepare NIC modifications
2805
    self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
2806

    
2807
    # OS change
2808
    if self.op.os_name and not self.op.force:
2809
      CheckNodeHasOS(self, self.instance.primary_node, self.op.os_name,
2810
                     self.op.force_variant)
2811
      instance_os = self.op.os_name
2812
    else:
2813
      instance_os = self.instance.os
2814

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

    
2818
    if self.op.disk_template:
2819
      self._PreCheckDiskTemplate(pnode_info)
2820

    
2821
    self._PreCheckDisks(ispec)
2822

    
2823
    # hvparams processing
2824
    if self.op.hvparams:
2825
      hv_type = self.instance.hypervisor
2826
      i_hvdict = GetUpdatedParams(self.instance.hvparams, self.op.hvparams)
2827
      utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
2828
      hv_new = self.cluster.SimpleFillHV(hv_type, self.instance.os, i_hvdict)
2829

    
2830
      # local check
2831
      hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
2832
      CheckHVParams(self, node_uuids, self.instance.hypervisor, hv_new)
2833
      self.hv_proposed = self.hv_new = hv_new # the new actual values
2834
      self.hv_inst = i_hvdict # the new dict (without defaults)
2835
    else:
2836
      self.hv_proposed = self.cluster.SimpleFillHV(self.instance.hypervisor,
2837
                                                   self.instance.os,
2838
                                                   self.instance.hvparams)
2839
      self.hv_new = self.hv_inst = {}
2840

    
2841
    # beparams processing
2842
    if self.op.beparams:
2843
      i_bedict = GetUpdatedParams(self.instance.beparams, self.op.beparams,
2844
                                  use_none=True)
2845
      objects.UpgradeBeParams(i_bedict)
2846
      utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
2847
      be_new = self.cluster.SimpleFillBE(i_bedict)
2848
      self.be_proposed = self.be_new = be_new # the new actual values
2849
      self.be_inst = i_bedict # the new dict (without defaults)
2850
    else:
2851
      self.be_new = self.be_inst = {}
2852
      self.be_proposed = self.cluster.SimpleFillBE(self.instance.beparams)
2853
    be_old = self.cluster.FillBE(self.instance)
2854

    
2855
    # CPU param validation -- checking every time a parameter is
2856
    # changed to cover all cases where either CPU mask or vcpus have
2857
    # changed
2858
    if (constants.BE_VCPUS in self.be_proposed and
2859
        constants.HV_CPU_MASK in self.hv_proposed):
2860
      cpu_list = \
2861
        utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
2862
      # Verify mask is consistent with number of vCPUs. Can skip this
2863
      # test if only 1 entry in the CPU mask, which means same mask
2864
      # is applied to all vCPUs.
2865
      if (len(cpu_list) > 1 and
2866
          len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
2867
        raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
2868
                                   " CPU mask [%s]" %
2869
                                   (self.be_proposed[constants.BE_VCPUS],
2870
                                    self.hv_proposed[constants.HV_CPU_MASK]),
2871
                                   errors.ECODE_INVAL)
2872

    
2873
      # Only perform this test if a new CPU mask is given
2874
      if constants.HV_CPU_MASK in self.hv_new:
2875
        # Calculate the largest CPU number requested
2876
        max_requested_cpu = max(map(max, cpu_list))
2877
        # Check that all of the instance's nodes have enough physical CPUs to
2878
        # satisfy the requested CPU mask
2879
        hvspecs = [(self.instance.hypervisor,
2880
                    self.cfg.GetClusterInfo()
2881
                      .hvparams[self.instance.hypervisor])]
2882
        _CheckNodesPhysicalCPUs(self, self.instance.all_nodes,
2883
                                max_requested_cpu + 1,
2884
                                hvspecs)
2885

    
2886
    # osparams processing
2887
    if self.op.osparams:
2888
      i_osdict = GetUpdatedParams(self.instance.osparams, self.op.osparams)
2889
      CheckOSParams(self, True, node_uuids, instance_os, i_osdict)
2890
      self.os_inst = i_osdict # the new dict (without defaults)
2891
    else:
2892
      self.os_inst = {}
2893

    
2894
    #TODO(dynmem): do the appropriate check involving MINMEM
2895
    if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
2896
        be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
2897
      mem_check_list = [pnode_uuid]
2898
      if be_new[constants.BE_AUTO_BALANCE]:
2899
        # either we changed auto_balance to yes or it was from before
2900
        mem_check_list.extend(self.instance.secondary_nodes)
2901
      instance_info = self.rpc.call_instance_info(
2902
          pnode_uuid, self.instance.name, self.instance.hypervisor,
2903
          self.instance.hvparams)
2904
      hvspecs = [(self.instance.hypervisor,
2905
                  self.cluster.hvparams[self.instance.hypervisor])]
2906
      nodeinfo = self.rpc.call_node_info(mem_check_list, None,
2907
                                         hvspecs)
2908
      pninfo = nodeinfo[pnode_uuid]
2909
      msg = pninfo.fail_msg
2910
      if msg:
2911
        # Assume the primary node is unreachable and go ahead
2912
        self.warn.append("Can't get info from primary node %s: %s" %
2913
                         (self.cfg.GetNodeName(pnode_uuid), msg))
2914
      else:
2915
        (_, _, (pnhvinfo, )) = pninfo.payload
2916
        if not isinstance(pnhvinfo.get("memory_free", None), int):
2917
          self.warn.append("Node data from primary node %s doesn't contain"
2918
                           " free memory information" %
2919
                           self.cfg.GetNodeName(pnode_uuid))
2920
        elif instance_info.fail_msg:
2921
          self.warn.append("Can't get instance runtime information: %s" %
2922
                           instance_info.fail_msg)
2923
        else:
2924
          if instance_info.payload:
2925
            current_mem = int(instance_info.payload["memory"])
2926
          else:
2927
            # Assume instance not running
2928
            # (there is a slight race condition here, but it's not very
2929
            # probable, and we have no other way to check)
2930
            # TODO: Describe race condition
2931
            current_mem = 0
2932
          #TODO(dynmem): do the appropriate check involving MINMEM
2933
          miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
2934
                      pnhvinfo["memory_free"])
2935
          if miss_mem > 0:
2936
            raise errors.OpPrereqError("This change will prevent the instance"
2937
                                       " from starting, due to %d MB of memory"
2938
                                       " missing on its primary node" %
2939
                                       miss_mem, errors.ECODE_NORES)
2940

    
2941
      if be_new[constants.BE_AUTO_BALANCE]:
2942
        for node_uuid, nres in nodeinfo.items():
2943
          if node_uuid not in self.instance.secondary_nodes:
2944
            continue
2945
          nres.Raise("Can't get info from secondary node %s" %
2946
                     self.cfg.GetNodeName(node_uuid), prereq=True,
2947
                     ecode=errors.ECODE_STATE)
2948
          (_, _, (nhvinfo, )) = nres.payload
2949
          if not isinstance(nhvinfo.get("memory_free", None), int):
2950
            raise errors.OpPrereqError("Secondary node %s didn't return free"
2951
                                       " memory information" %
2952
                                       self.cfg.GetNodeName(node_uuid),
2953
                                       errors.ECODE_STATE)
2954
          #TODO(dynmem): do the appropriate check involving MINMEM
2955
          elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
2956
            raise errors.OpPrereqError("This change will prevent the instance"
2957
                                       " from failover to its secondary node"
2958
                                       " %s, due to not enough memory" %
2959
                                       self.cfg.GetNodeName(node_uuid),
2960
                                       errors.ECODE_STATE)
2961

    
2962
    if self.op.runtime_mem:
2963
      remote_info = self.rpc.call_instance_info(
2964
         self.instance.primary_node, self.instance.name,
2965
         self.instance.hypervisor,
2966
         self.cluster.hvparams[self.instance.hypervisor])
2967
      remote_info.Raise("Error checking node %s" %
2968
                        self.cfg.GetNodeName(self.instance.primary_node))
2969
      if not remote_info.payload: # not running already
2970
        raise errors.OpPrereqError("Instance %s is not running" %
2971
                                   self.instance.name, errors.ECODE_STATE)
2972

    
2973
      current_memory = remote_info.payload["memory"]
2974
      if (not self.op.force and
2975
           (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
2976
            self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
2977
        raise errors.OpPrereqError("Instance %s must have memory between %d"
2978
                                   " and %d MB of memory unless --force is"
2979
                                   " given" %
2980
                                   (self.instance.name,
2981
                                    self.be_proposed[constants.BE_MINMEM],
2982
                                    self.be_proposed[constants.BE_MAXMEM]),
2983
                                   errors.ECODE_INVAL)
2984

    
2985
      delta = self.op.runtime_mem - current_memory
2986
      if delta > 0:
2987
        CheckNodeFreeMemory(
2988
            self, self.instance.primary_node,
2989
            "ballooning memory for instance %s" % self.instance.name, delta,
2990
            self.instance.hypervisor,
2991
            self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
2992

    
2993
    # make self.cluster visible in the functions below
2994
    cluster = self.cluster
2995

    
2996
    def _PrepareNicCreate(_, params, private):
2997
      self._PrepareNicModification(params, private, None, None,
2998
                                   {}, cluster, pnode_uuid)
2999
      return (None, None)
3000

    
3001
    def _PrepareNicMod(_, nic, params, private):
3002
      self._PrepareNicModification(params, private, nic.ip, nic.network,
3003
                                   nic.nicparams, cluster, pnode_uuid)
3004
      return None
3005

    
3006
    def _PrepareNicRemove(_, params, __):
3007
      ip = params.ip
3008
      net = params.network
3009
      if net is not None and ip is not None:
3010
        self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
3011

    
3012
    # Verify NIC changes (operating on copy)
3013
    nics = self.instance.nics[:]
3014
    _ApplyContainerMods("NIC", nics, None, self.nicmod,
3015
                        _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
3016
    if len(nics) > constants.MAX_NICS:
3017
      raise errors.OpPrereqError("Instance has too many network interfaces"
3018
                                 " (%d), cannot add more" % constants.MAX_NICS,
3019
                                 errors.ECODE_STATE)
3020

    
3021
    # Pre-compute NIC changes (necessary to use result in hooks)
3022
    self._nic_chgdesc = []
3023
    if self.nicmod:
3024
      # Operate on copies as this is still in prereq
3025
      nics = [nic.Copy() for nic in self.instance.nics]
3026
      _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
3027
                          self._CreateNewNic, self._ApplyNicMods, None)
3028
      # Verify that NIC names are unique and valid
3029
      utils.ValidateDeviceNames("NIC", nics)
3030
      self._new_nics = nics
3031
      ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
3032
    else:
3033
      self._new_nics = None
3034
      ispec[constants.ISPEC_NIC_COUNT] = len(self.instance.nics)
3035

    
3036
    if not self.op.ignore_ipolicy:
3037
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(self.cluster,
3038
                                                              group_info)
3039

    
3040
      # Fill ispec with backend parameters
3041
      ispec[constants.ISPEC_SPINDLE_USE] = \
3042
        self.be_new.get(constants.BE_SPINDLE_USE, None)
3043
      ispec[constants.ISPEC_CPU_COUNT] = self.be_new.get(constants.BE_VCPUS,
3044
                                                         None)
3045

    
3046
      # Copy ispec to verify parameters with min/max values separately
3047
      if self.op.disk_template:
3048
        new_disk_template = self.op.disk_template
3049
      else:
3050
        new_disk_template = self.instance.disk_template
3051
      ispec_max = ispec.copy()
3052
      ispec_max[constants.ISPEC_MEM_SIZE] = \
3053
        self.be_new.get(constants.BE_MAXMEM, None)
3054
      res_max = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_max,
3055
                                                     new_disk_template)
3056
      ispec_min = ispec.copy()
3057
      ispec_min[constants.ISPEC_MEM_SIZE] = \
3058
        self.be_new.get(constants.BE_MINMEM, None)
3059
      res_min = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
3060
                                                     new_disk_template)
3061

    
3062
      if (res_max or res_min):
3063
        # FIXME: Improve error message by including information about whether
3064
        # the upper or lower limit of the parameter fails the ipolicy.
3065
        msg = ("Instance allocation to group %s (%s) violates policy: %s" %
3066
               (group_info, group_info.name,
3067
                utils.CommaJoin(set(res_max + res_min))))
3068
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
3069

    
3070
  def _ConvertPlainToDrbd(self, feedback_fn):
3071
    """Converts an instance from plain to drbd.
3072

3073
    """
3074
    feedback_fn("Converting template to drbd")
3075
    pnode_uuid = self.instance.primary_node
3076
    snode_uuid = self.op.remote_node_uuid
3077

    
3078
    assert self.instance.disk_template == constants.DT_PLAIN
3079

    
3080
    # create a fake disk info for _GenerateDiskTemplate
3081
    disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
3082
                  constants.IDISK_VG: d.logical_id[0],
3083
                  constants.IDISK_NAME: d.name}
3084
                 for d in self.instance.disks]
3085
    new_disks = GenerateDiskTemplate(self, self.op.disk_template,
3086
                                     self.instance.uuid, pnode_uuid,
3087
                                     [snode_uuid], disk_info, None, None, 0,
3088
                                     feedback_fn, self.diskparams)
3089
    anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
3090
                                        self.diskparams)
3091
    p_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, pnode_uuid)
3092
    s_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, snode_uuid)
3093
    info = GetInstanceInfoText(self.instance)
3094
    feedback_fn("Creating additional volumes...")
3095
    # first, create the missing data and meta devices
3096
    for disk in anno_disks:
3097
      # unfortunately this is... not too nice
3098
      CreateSingleBlockDev(self, pnode_uuid, self.instance, disk.children[1],
3099
                           info, True, p_excl_stor)
3100
      for child in disk.children:
3101
        CreateSingleBlockDev(self, snode_uuid, self.instance, child, info, True,
3102
                             s_excl_stor)
3103
    # at this stage, all new LVs have been created, we can rename the
3104
    # old ones
3105
    feedback_fn("Renaming original volumes...")
3106
    rename_list = [(o, n.children[0].logical_id)
3107
                   for (o, n) in zip(self.instance.disks, new_disks)]
3108
    result = self.rpc.call_blockdev_rename(pnode_uuid, rename_list)
3109
    result.Raise("Failed to rename original LVs")
3110

    
3111
    feedback_fn("Initializing DRBD devices...")
3112
    # all child devices are in place, we can now create the DRBD devices
3113
    try:
3114
      for disk in anno_disks:
3115
        for (node_uuid, excl_stor) in [(pnode_uuid, p_excl_stor),
3116
                                       (snode_uuid, s_excl_stor)]:
3117
          f_create = node_uuid == pnode_uuid
3118
          CreateSingleBlockDev(self, node_uuid, self.instance, disk, info,
3119
                               f_create, excl_stor)
3120
    except errors.GenericError, e:
3121
      feedback_fn("Initializing of DRBD devices failed;"
3122
                  " renaming back original volumes...")
3123
      for disk in new_disks:
3124
        self.cfg.SetDiskID(disk, pnode_uuid)
3125
      rename_back_list = [(n.children[0], o.logical_id)
3126
                          for (n, o) in zip(new_disks, self.instance.disks)]
3127
      result = self.rpc.call_blockdev_rename(pnode_uuid, rename_back_list)
3128
      result.Raise("Failed to rename LVs back after error %s" % str(e))
3129
      raise
3130

    
3131
    # at this point, the instance has been modified
3132
    self.instance.disk_template = constants.DT_DRBD8
3133
    self.instance.disks = new_disks
3134
    self.cfg.Update(self.instance, feedback_fn)
3135

    
3136
    # Release node locks while waiting for sync
3137
    ReleaseLocks(self, locking.LEVEL_NODE)
3138

    
3139
    # disks are created, waiting for sync
3140
    disk_abort = not WaitForSync(self, self.instance,
3141
                                 oneshot=not self.op.wait_for_sync)
3142
    if disk_abort:
3143
      raise errors.OpExecError("There are some degraded disks for"
3144
                               " this instance, please cleanup manually")
3145

    
3146
    # Node resource locks will be released by caller
3147

    
3148
  def _ConvertDrbdToPlain(self, feedback_fn):
3149
    """Converts an instance from drbd to plain.
3150

3151
    """
3152
    assert len(self.instance.secondary_nodes) == 1
3153
    assert self.instance.disk_template == constants.DT_DRBD8
3154

    
3155
    pnode_uuid = self.instance.primary_node
3156
    snode_uuid = self.instance.secondary_nodes[0]
3157
    feedback_fn("Converting template to plain")
3158

    
3159
    old_disks = AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
3160
    new_disks = [d.children[0] for d in self.instance.disks]
3161

    
3162
    # copy over size, mode and name
3163
    for parent, child in zip(old_disks, new_disks):
3164
      child.size = parent.size
3165
      child.mode = parent.mode
3166
      child.name = parent.name
3167

    
3168
    # this is a DRBD disk, return its port to the pool
3169
    # NOTE: this must be done right before the call to cfg.Update!
3170
    for disk in old_disks:
3171
      tcp_port = disk.logical_id[2]
3172
      self.cfg.AddTcpUdpPort(tcp_port)
3173

    
3174
    # update instance structure
3175
    self.instance.disks = new_disks
3176
    self.instance.disk_template = constants.DT_PLAIN
3177
    _UpdateIvNames(0, self.instance.disks)
3178
    self.cfg.Update(self.instance, feedback_fn)
3179

    
3180
    # Release locks in case removing disks takes a while
3181
    ReleaseLocks(self, locking.LEVEL_NODE)
3182

    
3183
    feedback_fn("Removing volumes on the secondary node...")
3184
    for disk in old_disks:
3185
      self.cfg.SetDiskID(disk, snode_uuid)
3186
      msg = self.rpc.call_blockdev_remove(snode_uuid, disk).fail_msg
3187
      if msg:
3188
        self.LogWarning("Could not remove block device %s on node %s,"
3189
                        " continuing anyway: %s", disk.iv_name,
3190
                        self.cfg.GetNodeName(snode_uuid), msg)
3191

    
3192
    feedback_fn("Removing unneeded volumes on the primary node...")
3193
    for idx, disk in enumerate(old_disks):
3194
      meta = disk.children[1]
3195
      self.cfg.SetDiskID(meta, pnode_uuid)
3196
      msg = self.rpc.call_blockdev_remove(pnode_uuid, meta).fail_msg
3197
      if msg:
3198
        self.LogWarning("Could not remove metadata for disk %d on node %s,"
3199
                        " continuing anyway: %s", idx,
3200
                        self.cfg.GetNodeName(pnode_uuid), msg)
3201

    
3202
  def _CreateNewDisk(self, idx, params, _):
3203
    """Creates a new disk.
3204

3205
    """
3206
    # add a new disk
3207
    if self.instance.disk_template in constants.DTS_FILEBASED:
3208
      (file_driver, file_path) = self.instance.disks[0].logical_id
3209
      file_path = os.path.dirname(file_path)
3210
    else:
3211
      file_driver = file_path = None
3212

    
3213
    disk = \
3214
      GenerateDiskTemplate(self, self.instance.disk_template,
3215
                           self.instance.uuid, self.instance.primary_node,
3216
                           self.instance.secondary_nodes, [params], file_path,
3217
                           file_driver, idx, self.Log, self.diskparams)[0]
3218

    
3219
    new_disks = CreateDisks(self, self.instance, disks=[disk])
3220

    
3221
    if self.cluster.prealloc_wipe_disks:
3222
      # Wipe new disk
3223
      WipeOrCleanupDisks(self, self.instance,
3224
                         disks=[(idx, disk, 0)],
3225
                         cleanup=new_disks)
3226

    
3227
    return (disk, [
3228
      ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
3229
      ])
3230

    
3231
  @staticmethod
3232
  def _ModifyDisk(idx, disk, params, _):
3233
    """Modifies a disk.
3234

3235
    """
3236
    changes = []
3237
    mode = params.get(constants.IDISK_MODE, None)
3238
    if mode:
3239
      disk.mode = mode
3240
      changes.append(("disk.mode/%d" % idx, disk.mode))
3241

    
3242
    name = params.get(constants.IDISK_NAME, None)
3243
    disk.name = name
3244
    changes.append(("disk.name/%d" % idx, disk.name))
3245

    
3246
    return changes
3247

    
3248
  def _RemoveDisk(self, idx, root, _):
3249
    """Removes a disk.
3250

3251
    """
3252
    (anno_disk,) = AnnotateDiskParams(self.instance, [root], self.cfg)
3253
    for node_uuid, disk in anno_disk.ComputeNodeTree(
3254
                             self.instance.primary_node):
3255
      self.cfg.SetDiskID(disk, node_uuid)
3256
      msg = self.rpc.call_blockdev_remove(node_uuid, disk).fail_msg
3257
      if msg:
3258
        self.LogWarning("Could not remove disk/%d on node '%s': %s,"
3259
                        " continuing anyway", idx,
3260
                        self.cfg.GetNodeName(node_uuid), msg)
3261

    
3262
    # if this is a DRBD disk, return its port to the pool
3263
    if root.dev_type in constants.LDS_DRBD:
3264
      self.cfg.AddTcpUdpPort(root.logical_id[2])
3265

    
3266
  def _CreateNewNic(self, idx, params, private):
3267
    """Creates data structure for a new network interface.
3268

3269
    """
3270
    mac = params[constants.INIC_MAC]
3271
    ip = params.get(constants.INIC_IP, None)
3272
    net = params.get(constants.INIC_NETWORK, None)
3273
    name = params.get(constants.INIC_NAME, None)
3274
    net_uuid = self.cfg.LookupNetwork(net)
3275
    #TODO: not private.filled?? can a nic have no nicparams??
3276
    nicparams = private.filled
3277
    nobj = objects.NIC(mac=mac, ip=ip, network=net_uuid, name=name,
3278
                       nicparams=nicparams)
3279
    nobj.uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
3280

    
3281
    return (nobj, [
3282
      ("nic.%d" % idx,
3283
       "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" %
3284
       (mac, ip, private.filled[constants.NIC_MODE],
3285
       private.filled[constants.NIC_LINK],
3286
       net)),
3287
      ])
3288

    
3289
  def _ApplyNicMods(self, idx, nic, params, private):
3290
    """Modifies a network interface.
3291

3292
    """
3293
    changes = []
3294

    
3295
    for key in [constants.INIC_MAC, constants.INIC_IP, constants.INIC_NAME]:
3296
      if key in params:
3297
        changes.append(("nic.%s/%d" % (key, idx), params[key]))
3298
        setattr(nic, key, params[key])
3299

    
3300
    new_net = params.get(constants.INIC_NETWORK, nic.network)
3301
    new_net_uuid = self.cfg.LookupNetwork(new_net)
3302
    if new_net_uuid != nic.network:
3303
      changes.append(("nic.network/%d" % idx, new_net))
3304
      nic.network = new_net_uuid
3305

    
3306
    if private.filled:
3307
      nic.nicparams = private.filled
3308

    
3309
      for (key, val) in nic.nicparams.items():
3310
        changes.append(("nic.%s/%d" % (key, idx), val))
3311

    
3312
    return changes
3313

    
3314
  def Exec(self, feedback_fn):
3315
    """Modifies an instance.
3316

3317
    All parameters take effect only at the next restart of the instance.
3318

3319
    """
3320
    # Process here the warnings from CheckPrereq, as we don't have a
3321
    # feedback_fn there.
3322
    # TODO: Replace with self.LogWarning
3323
    for warn in self.warn:
3324
      feedback_fn("WARNING: %s" % warn)
3325

    
3326
    assert ((self.op.disk_template is None) ^
3327
            bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
3328
      "Not owning any node resource locks"
3329

    
3330
    result = []
3331

    
3332
    # New primary node
3333
    if self.op.pnode_uuid:
3334
      self.instance.primary_node = self.op.pnode_uuid
3335

    
3336
    # runtime memory
3337
    if self.op.runtime_mem:
3338
      rpcres = self.rpc.call_instance_balloon_memory(self.instance.primary_node,
3339
                                                     self.instance,
3340
                                                     self.op.runtime_mem)
3341
      rpcres.Raise("Cannot modify instance runtime memory")
3342
      result.append(("runtime_memory", self.op.runtime_mem))
3343

    
3344
    # Apply disk changes
3345
    _ApplyContainerMods("disk", self.instance.disks, result, self.diskmod,
3346
                        self._CreateNewDisk, self._ModifyDisk,
3347
                        self._RemoveDisk)
3348
    _UpdateIvNames(0, self.instance.disks)
3349

    
3350
    if self.op.disk_template:
3351
      if __debug__:
3352
        check_nodes = set(self.instance.all_nodes)
3353
        if self.op.remote_node_uuid:
3354
          check_nodes.add(self.op.remote_node_uuid)
3355
        for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
3356
          owned = self.owned_locks(level)
3357
          assert not (check_nodes - owned), \
3358
            ("Not owning the correct locks, owning %r, expected at least %r" %
3359
             (owned, check_nodes))
3360

    
3361
      r_shut = ShutdownInstanceDisks(self, self.instance)
3362
      if not r_shut:
3363
        raise errors.OpExecError("Cannot shutdown instance disks, unable to"
3364
                                 " proceed with disk template conversion")
3365
      mode = (self.instance.disk_template, self.op.disk_template)
3366
      try:
3367
        self._DISK_CONVERSIONS[mode](self, feedback_fn)
3368
      except:
3369
        self.cfg.ReleaseDRBDMinors(self.instance.uuid)
3370
        raise
3371
      result.append(("disk_template", self.op.disk_template))
3372

    
3373
      assert self.instance.disk_template == self.op.disk_template, \
3374
        ("Expected disk template '%s', found '%s'" %
3375
         (self.op.disk_template, self.instance.disk_template))
3376

    
3377
    # Release node and resource locks if there are any (they might already have
3378
    # been released during disk conversion)
3379
    ReleaseLocks(self, locking.LEVEL_NODE)
3380
    ReleaseLocks(self, locking.LEVEL_NODE_RES)
3381

    
3382
    # Apply NIC changes
3383
    if self._new_nics is not None:
3384
      self.instance.nics = self._new_nics
3385
      result.extend(self._nic_chgdesc)
3386

    
3387
    # hvparams changes
3388
    if self.op.hvparams:
3389
      self.instance.hvparams = self.hv_inst
3390
      for key, val in self.op.hvparams.iteritems():
3391
        result.append(("hv/%s" % key, val))
3392

    
3393
    # beparams changes
3394
    if self.op.beparams:
3395
      self.instance.beparams = self.be_inst
3396
      for key, val in self.op.beparams.iteritems():
3397
        result.append(("be/%s" % key, val))
3398

    
3399
    # OS change
3400
    if self.op.os_name:
3401
      self.instance.os = self.op.os_name
3402

    
3403
    # osparams changes
3404
    if self.op.osparams:
3405
      self.instance.osparams = self.os_inst
3406
      for key, val in self.op.osparams.iteritems():
3407
        result.append(("os/%s" % key, val))
3408

    
3409
    if self.op.offline is None:
3410
      # Ignore
3411
      pass
3412
    elif self.op.offline:
3413
      # Mark instance as offline
3414
      self.cfg.MarkInstanceOffline(self.instance.uuid)
3415
      result.append(("admin_state", constants.ADMINST_OFFLINE))
3416
    else:
3417
      # Mark instance as online, but stopped
3418
      self.cfg.MarkInstanceDown(self.instance.uuid)
3419
      result.append(("admin_state", constants.ADMINST_DOWN))
3420

    
3421
    self.cfg.Update(self.instance, feedback_fn, self.proc.GetECId())
3422

    
3423
    assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
3424
                self.owned_locks(locking.LEVEL_NODE)), \
3425
      "All node locks should have been released by now"
3426

    
3427
    return result
3428

    
3429
  _DISK_CONVERSIONS = {
3430
    (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
3431
    (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
3432
    }
3433

    
3434

    
3435
class LUInstanceChangeGroup(LogicalUnit):
3436
  HPATH = "instance-change-group"
3437
  HTYPE = constants.HTYPE_INSTANCE
3438
  REQ_BGL = False
3439

    
3440
  def ExpandNames(self):
3441
    self.share_locks = ShareAll()
3442

    
3443
    self.needed_locks = {
3444
      locking.LEVEL_NODEGROUP: [],
3445
      locking.LEVEL_NODE: [],
3446
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
3447
      }
3448

    
3449
    self._ExpandAndLockInstance()
3450

    
3451
    if self.op.target_groups:
3452
      self.req_target_uuids = map(self.cfg.LookupNodeGroup,
3453
                                  self.op.target_groups)
3454
    else:
3455
      self.req_target_uuids = None
3456

    
3457
    self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
3458

    
3459
  def DeclareLocks(self, level):
3460
    if level == locking.LEVEL_NODEGROUP:
3461
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3462

    
3463
      if self.req_target_uuids:
3464
        lock_groups = set(self.req_target_uuids)
3465

    
3466
        # Lock all groups used by instance optimistically; this requires going
3467
        # via the node before it's locked, requiring verification later on
3468
        instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
3469
        lock_groups.update(instance_groups)
3470
      else:
3471
        # No target groups, need to lock all of them
3472
        lock_groups = locking.ALL_SET
3473

    
3474
      self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
3475

    
3476
    elif level == locking.LEVEL_NODE:
3477
      if self.req_target_uuids:
3478
        # Lock all nodes used by instances
3479
        self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3480
        self._LockInstancesNodes()
3481

    
3482
        # Lock all nodes in all potential target groups
3483
        lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
3484
                       self.cfg.GetInstanceNodeGroups(self.op.instance_uuid))
3485
        member_nodes = [node_uuid
3486
                        for group in lock_groups
3487
                        for node_uuid in self.cfg.GetNodeGroup(group).members]
3488
        self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3489
      else:
3490
        # Lock all nodes as all groups are potential targets
3491
        self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3492

    
3493
  def CheckPrereq(self):
3494
    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3495
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3496
    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3497

    
3498
    assert (self.req_target_uuids is None or
3499
            owned_groups.issuperset(self.req_target_uuids))
3500
    assert owned_instance_names == set([self.op.instance_name])
3501

    
3502
    # Get instance information
3503
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
3504

    
3505
    # Check if node groups for locked instance are still correct
3506
    assert owned_nodes.issuperset(self.instance.all_nodes), \
3507
      ("Instance %s's nodes changed while we kept the lock" %
3508
       self.op.instance_name)
3509

    
3510
    inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_uuid,
3511
                                          owned_groups)
3512

    
3513
    if self.req_target_uuids:
3514
      # User requested specific target groups
3515
      self.target_uuids = frozenset(self.req_target_uuids)
3516
    else:
3517
      # All groups except those used by the instance are potential targets
3518
      self.target_uuids = owned_groups - inst_groups
3519

    
3520
    conflicting_groups = self.target_uuids & inst_groups
3521
    if conflicting_groups:
3522
      raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
3523
                                 " used by the instance '%s'" %
3524
                                 (utils.CommaJoin(conflicting_groups),
3525
                                  self.op.instance_name),
3526
                                 errors.ECODE_INVAL)
3527

    
3528
    if not self.target_uuids:
3529
      raise errors.OpPrereqError("There are no possible target groups",
3530
                                 errors.ECODE_INVAL)
3531

    
3532
  def BuildHooksEnv(self):
3533
    """Build hooks env.
3534

3535
    """
3536
    assert self.target_uuids
3537

    
3538
    env = {
3539
      "TARGET_GROUPS": " ".join(self.target_uuids),
3540
      }
3541

    
3542
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
3543

    
3544
    return env
3545

    
3546
  def BuildHooksNodes(self):
3547
    """Build hooks nodes.
3548

3549
    """
3550
    mn = self.cfg.GetMasterNode()
3551
    return ([mn], [mn])
3552

    
3553
  def Exec(self, feedback_fn):
3554
    instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
3555

    
3556
    assert instances == [self.op.instance_name], "Instance not locked"
3557

    
3558
    req = iallocator.IAReqGroupChange(instances=instances,
3559
                                      target_groups=list(self.target_uuids))
3560
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
3561

    
3562
    ial.Run(self.op.iallocator)
3563

    
3564
    if not ial.success:
3565
      raise errors.OpPrereqError("Can't compute solution for changing group of"
3566
                                 " instance '%s' using iallocator '%s': %s" %
3567
                                 (self.op.instance_name, self.op.iallocator,
3568
                                  ial.info), errors.ECODE_NORES)
3569

    
3570
    jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
3571

    
3572
    self.LogInfo("Iallocator returned %s job(s) for changing group of"
3573
                 " instance '%s'", len(jobs), self.op.instance_name)
3574

    
3575
    return ResultWithJobs(jobs)