Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance.py @ 8aa8f6b1

History | View | Annotate | Download (134.9 kB)

1
#
2
#
3

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

    
21

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

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

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

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

    
46
from ganeti.cmdlib.common import INSTANCE_DOWN, \
47
  INSTANCE_NOT_RUNNING, CAN_CHANGE_INSTANCE_OFFLINE, _CheckNodeOnline, \
48
  _ShareAll, _GetDefaultIAllocator, _CheckInstanceNodeGroups, \
49
  _LoadNodeEvacResult, _CheckIAllocatorOrNode, _CheckParamsNotGlobal, \
50
  _IsExclusiveStorageEnabledNode, _CheckHVParams, _CheckOSParams, \
51
  _AnnotateDiskParams, _GetUpdatedParams, _ExpandInstanceName, \
52
  _ComputeIPolicySpecViolation, _CheckInstanceState, _ExpandNodeName
53
from ganeti.cmdlib.instance_storage import _CreateDisks, \
54
  _CheckNodesFreeDiskPerVG, _WipeDisks, _WaitForSync, \
55
  _IsExclusiveStorageEnabledNodeName, _CreateSingleBlockDev, _ComputeDisks, \
56
  _CheckRADOSFreeSpace, _ComputeDiskSizePerVG, _GenerateDiskTemplate, \
57
  _CreateBlockDev, _StartInstanceDisks, _ShutdownInstanceDisks, \
58
  _AssembleInstanceDisks
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_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_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_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

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

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

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

    
207
      nic_ip = ip
208

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

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

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

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

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

    
245
  return nics
246

    
247

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

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

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

    
264
  return (None, None)
265

    
266

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

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

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

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

    
292

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

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

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

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

    
316

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

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

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

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

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

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

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

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

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

    
391
    self.adopt_disks = has_adopt
392

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

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

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

    
413
    ### Node/iallocator related checks
414
    _CheckIAllocatorOrNode(self, "iallocator", "pnode")
415

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

    
426
    _CheckOpportunisticLocking(self.op)
427

    
428
    self._cds = _GetClusterDomainSecret()
429

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

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

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

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

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

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

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

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

    
482
      self.source_x509_ca = cert
483

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

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

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

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

499
    Figure out the right locks for instance creation.
500

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

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

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

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

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

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

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

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

    
555
    self.needed_locks[locking.LEVEL_NODE_RES] = \
556
      _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
557

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

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

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

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

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

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

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

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

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

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

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

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

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

    
632
    return env
633

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

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

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

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

647
    @return: the export information
648

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

835
    """
836
    self._CalculateFileStorageDir()
837

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

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

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

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

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

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

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

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

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

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

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

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

    
908
      self.src_images = disk_images
909

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

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

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

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

    
937
    #### allocator run
938

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

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

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

    
952
    #### node related checks
953

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

    
968
    self.secondaries = []
969

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1161
    self.dry_run_result = list(nodenames)
1162

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1280
    # Release all node resource locks
1281
    _ReleaseLocks(self, locking.LEVEL_NODE_RES)
1282

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

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

    
1318
          os_add_result.Raise("Could not add os for instance %s"
1319
                              " on node %s" % (instance, pnode_name))
1320

    
1321
      else:
1322
        if self.op.mode == constants.INSTANCE_IMPORT:
1323
          feedback_fn("* running the instance OS import scripts...")
1324

    
1325
          transfers = []
1326

    
1327
          for idx, image in enumerate(self.src_images):
1328
            if not image:
1329
              continue
1330

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

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

    
1348
          rename_from = self._old_instance_name
1349

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

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

    
1371
          rename_from = self.source_instance_name
1372

    
1373
        else:
1374
          # also checked in the prereq part
1375
          raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
1376
                                       % self.op.mode)
1377

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

    
1388
    assert not self.owned_locks(locking.LEVEL_NODE_RES)
1389

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

    
1399
    return list(iobj.all_nodes)
1400

    
1401

    
1402
class LUInstanceRename(LogicalUnit):
1403
  """Rename an instance.
1404

1405
  """
1406
  HPATH = "instance-rename"
1407
  HTYPE = constants.HTYPE_INSTANCE
1408

    
1409
  def CheckArguments(self):
1410
    """Check arguments.
1411

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

    
1418
  def BuildHooksEnv(self):
1419
    """Build hooks env.
1420

1421
    This runs on master, primary and secondary nodes of the instance.
1422

1423
    """
1424
    env = _BuildInstanceHookEnvByObject(self, self.instance)
1425
    env["INSTANCE_NEW_NAME"] = self.op.new_name
1426
    return env
1427

    
1428
  def BuildHooksNodes(self):
1429
    """Build hooks nodes.
1430

1431
    """
1432
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
1433
    return (nl, nl)
1434

    
1435
  def CheckPrereq(self):
1436
    """Check prerequisites.
1437

1438
    This checks that the instance is in the cluster and is not running.
1439

1440
    """
1441
    self.op.instance_name = _ExpandInstanceName(self.cfg,
1442
                                                self.op.instance_name)
1443
    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1444
    assert instance is not None
1445
    _CheckNodeOnline(self, instance.primary_node)
1446
    _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
1447
                        msg="cannot rename")
1448
    self.instance = instance
1449

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

    
1460
    instance_list = self.cfg.GetInstanceList()
1461
    if new_name in instance_list and new_name != instance.name:
1462
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
1463
                                 new_name, errors.ECODE_EXISTS)
1464

    
1465
  def Exec(self, feedback_fn):
1466
    """Rename the instance.
1467

1468
    """
1469
    inst = self.instance
1470
    old_name = inst.name
1471

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

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

    
1486
    # re-read the instance from the configuration after rename
1487
    inst = self.cfg.GetInstanceInfo(self.op.new_name)
1488

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

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

    
1521
    return inst.name
1522

    
1523

    
1524
class LUInstanceRemove(LogicalUnit):
1525
  """Remove an instance.
1526

1527
  """
1528
  HPATH = "instance-remove"
1529
  HTYPE = constants.HTYPE_INSTANCE
1530
  REQ_BGL = False
1531

    
1532
  def ExpandNames(self):
1533
    self._ExpandAndLockInstance()
1534
    self.needed_locks[locking.LEVEL_NODE] = []
1535
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1536
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
1537

    
1538
  def DeclareLocks(self, level):
1539
    if level == locking.LEVEL_NODE:
1540
      self._LockInstancesNodes()
1541
    elif level == locking.LEVEL_NODE_RES:
1542
      # Copy node locks
1543
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1544
        _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1545

    
1546
  def BuildHooksEnv(self):
1547
    """Build hooks env.
1548

1549
    This runs on master, primary and secondary nodes of the instance.
1550

1551
    """
1552
    env = _BuildInstanceHookEnvByObject(self, self.instance)
1553
    env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
1554
    return env
1555

    
1556
  def BuildHooksNodes(self):
1557
    """Build hooks nodes.
1558

1559
    """
1560
    nl = [self.cfg.GetMasterNode()]
1561
    nl_post = list(self.instance.all_nodes) + nl
1562
    return (nl, nl_post)
1563

    
1564
  def CheckPrereq(self):
1565
    """Check prerequisites.
1566

1567
    This checks that the instance is in the cluster.
1568

1569
    """
1570
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1571
    assert self.instance is not None, \
1572
      "Cannot retrieve locked instance %s" % self.op.instance_name
1573

    
1574
  def Exec(self, feedback_fn):
1575
    """Remove the instance.
1576

1577
    """
1578
    instance = self.instance
1579
    logging.info("Shutting down instance %s on node %s",
1580
                 instance.name, instance.primary_node)
1581

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

    
1594
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1595
            self.owned_locks(locking.LEVEL_NODE_RES))
1596
    assert not (set(instance.all_nodes) -
1597
                self.owned_locks(locking.LEVEL_NODE)), \
1598
      "Not owning correct locks"
1599

    
1600
    _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
1601

    
1602

    
1603
class LUInstanceMove(LogicalUnit):
1604
  """Move an instance by data-copying.
1605

1606
  """
1607
  HPATH = "instance-move"
1608
  HTYPE = constants.HTYPE_INSTANCE
1609
  REQ_BGL = False
1610

    
1611
  def ExpandNames(self):
1612
    self._ExpandAndLockInstance()
1613
    target_node = _ExpandNodeName(self.cfg, self.op.target_node)
1614
    self.op.target_node = target_node
1615
    self.needed_locks[locking.LEVEL_NODE] = [target_node]
1616
    self.needed_locks[locking.LEVEL_NODE_RES] = []
1617
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
1618

    
1619
  def DeclareLocks(self, level):
1620
    if level == locking.LEVEL_NODE:
1621
      self._LockInstancesNodes(primary_only=True)
1622
    elif level == locking.LEVEL_NODE_RES:
1623
      # Copy node locks
1624
      self.needed_locks[locking.LEVEL_NODE_RES] = \
1625
        _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
1626

    
1627
  def BuildHooksEnv(self):
1628
    """Build hooks env.
1629

1630
    This runs on master, primary and secondary nodes of the instance.
1631

1632
    """
1633
    env = {
1634
      "TARGET_NODE": self.op.target_node,
1635
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
1636
      }
1637
    env.update(_BuildInstanceHookEnvByObject(self, self.instance))
1638
    return env
1639

    
1640
  def BuildHooksNodes(self):
1641
    """Build hooks nodes.
1642

1643
    """
1644
    nl = [
1645
      self.cfg.GetMasterNode(),
1646
      self.instance.primary_node,
1647
      self.op.target_node,
1648
      ]
1649
    return (nl, nl)
1650

    
1651
  def CheckPrereq(self):
1652
    """Check prerequisites.
1653

1654
    This checks that the instance is in the cluster.
1655

1656
    """
1657
    self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
1658
    assert self.instance is not None, \
1659
      "Cannot retrieve locked instance %s" % self.op.instance_name
1660

    
1661
    if instance.disk_template not in constants.DTS_COPYABLE:
1662
      raise errors.OpPrereqError("Disk template %s not suitable for copying" %
1663
                                 instance.disk_template, errors.ECODE_STATE)
1664

    
1665
    node = self.cfg.GetNodeInfo(self.op.target_node)
1666
    assert node is not None, \
1667
      "Cannot retrieve locked node %s" % self.op.target_node
1668

    
1669
    self.target_node = target_node = node.name
1670

    
1671
    if target_node == instance.primary_node:
1672
      raise errors.OpPrereqError("Instance %s is already on the node %s" %
1673
                                 (instance.name, target_node),
1674
                                 errors.ECODE_STATE)
1675

    
1676
    bep = self.cfg.GetClusterInfo().FillBE(instance)
1677

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

    
1683
    _CheckNodeOnline(self, target_node)
1684
    _CheckNodeNotDrained(self, target_node)
1685
    _CheckNodeVmCapable(self, target_node)
1686
    cluster = self.cfg.GetClusterInfo()
1687
    group_info = self.cfg.GetNodeGroup(node.group)
1688
    ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
1689
    _CheckTargetNodeIPolicy(self, ipolicy, instance, node, self.cfg,
1690
                            ignore=self.op.ignore_ipolicy)
1691

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

    
1702
    # check bridge existance
1703
    _CheckInstanceBridgesExist(self, instance, node=target_node)
1704

    
1705
  def Exec(self, feedback_fn):
1706
    """Move an instance.
1707

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

1711
    """
1712
    instance = self.instance
1713

    
1714
    source_node = instance.primary_node
1715
    target_node = self.target_node
1716

    
1717
    self.LogInfo("Shutting down instance %s on source node %s",
1718
                 instance.name, source_node)
1719

    
1720
    assert (self.owned_locks(locking.LEVEL_NODE) ==
1721
            self.owned_locks(locking.LEVEL_NODE_RES))
1722

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

    
1738
    # create the target disks
1739
    try:
1740
      _CreateDisks(self, instance, target_node=target_node)
1741
    except errors.OpExecError:
1742
      self.LogWarning("Device creation failed")
1743
      self.cfg.ReleaseDRBDMinors(instance.name)
1744
      raise
1745

    
1746
    cluster_name = self.cfg.GetClusterInfo().cluster_name
1747

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

    
1769
    if errs:
1770
      self.LogWarning("Some disks failed to copy, aborting")
1771
      try:
1772
        _RemoveDisks(self, instance, target_node=target_node)
1773
      finally:
1774
        self.cfg.ReleaseDRBDMinors(instance.name)
1775
        raise errors.OpExecError("Errors during disk copy: %s" %
1776
                                 (",".join(errs),))
1777

    
1778
    instance.primary_node = target_node
1779
    self.cfg.Update(instance, feedback_fn)
1780

    
1781
    self.LogInfo("Removing the disks on the original node")
1782
    _RemoveDisks(self, instance, target_node=source_node)
1783

    
1784
    # Only start the instance if it's marked as up
1785
    if instance.admin_state == constants.ADMINST_UP:
1786
      self.LogInfo("Starting instance %s on node %s",
1787
                   instance.name, target_node)
1788

    
1789
      disks_ok, _ = _AssembleInstanceDisks(self, instance,
1790
                                           ignore_secondaries=True)
1791
      if not disks_ok:
1792
        _ShutdownInstanceDisks(self, instance)
1793
        raise errors.OpExecError("Can't activate the instance's disks")
1794

    
1795
      result = self.rpc.call_instance_start(target_node,
1796
                                            (instance, None, None), False,
1797
                                            self.op.reason)
1798
      msg = result.fail_msg
1799
      if msg:
1800
        _ShutdownInstanceDisks(self, instance)
1801
        raise errors.OpExecError("Could not start instance %s on node %s: %s" %
1802
                                 (instance.name, target_node, msg))
1803

    
1804

    
1805
class LUInstanceMultiAlloc(NoHooksLU):
1806
  """Allocates multiple instances at the same time.
1807

1808
  """
1809
  REQ_BGL = False
1810

    
1811
  def CheckArguments(self):
1812
    """Check arguments.
1813

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

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

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

    
1842
    _CheckOpportunisticLocking(self.op)
1843

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

    
1849
  def ExpandNames(self):
1850
    """Calculate the locks.
1851

1852
    """
1853
    self.share_locks = _ShareAll()
1854
    self.needed_locks = {
1855
      # iallocator will select nodes and even if no iallocator is used,
1856
      # collisions with LUInstanceCreate should be avoided
1857
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
1858
      }
1859

    
1860
    if self.op.iallocator:
1861
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
1862
      self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
1863

    
1864
      if self.op.opportunistic_locking:
1865
        self.opportunistic_locks[locking.LEVEL_NODE] = True
1866
        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
1867
    else:
1868
      nodeslist = []
1869
      for inst in self.op.instances:
1870
        inst.pnode = _ExpandNodeName(self.cfg, inst.pnode)
1871
        nodeslist.append(inst.pnode)
1872
        if inst.snode is not None:
1873
          inst.snode = _ExpandNodeName(self.cfg, inst.snode)
1874
          nodeslist.append(inst.snode)
1875

    
1876
      self.needed_locks[locking.LEVEL_NODE] = nodeslist
1877
      # Lock resources of instance's primary and secondary nodes (copy to
1878
      # prevent accidential modification)
1879
      self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
1880

    
1881
  def CheckPrereq(self):
1882
    """Check prerequisite.
1883

1884
    """
1885
    cluster = self.cfg.GetClusterInfo()
1886
    default_vg = self.cfg.GetVGName()
1887
    ec_id = self.proc.GetECId()
1888

    
1889
    if self.op.opportunistic_locking:
1890
      # Only consider nodes for which a lock is held
1891
      node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
1892
    else:
1893
      node_whitelist = None
1894

    
1895
    insts = [_CreateInstanceAllocRequest(op, _ComputeDisks(op, default_vg),
1896
                                         _ComputeNics(op, cluster, None,
1897
                                                      self.cfg, ec_id),
1898
                                         _ComputeFullBeParams(op, cluster),
1899
                                         node_whitelist)
1900
             for op in self.op.instances]
1901

    
1902
    req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
1903
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
1904

    
1905
    ial.Run(self.op.iallocator)
1906

    
1907
    if not ial.success:
1908
      raise errors.OpPrereqError("Can't compute nodes using"
1909
                                 " iallocator '%s': %s" %
1910
                                 (self.op.iallocator, ial.info),
1911
                                 errors.ECODE_NORES)
1912

    
1913
    self.ia_result = ial.result
1914

    
1915
    if self.op.dry_run:
1916
      self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
1917
        constants.JOB_IDS_KEY: [],
1918
        })
1919

    
1920
  def _ConstructPartialResult(self):
1921
    """Contructs the partial result.
1922

1923
    """
1924
    (allocatable, failed) = self.ia_result
1925
    return {
1926
      opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY:
1927
        map(compat.fst, allocatable),
1928
      opcodes.OpInstanceMultiAlloc.FAILED_KEY: failed,
1929
      }
1930

    
1931
  def Exec(self, feedback_fn):
1932
    """Executes the opcode.
1933

1934
    """
1935
    op2inst = dict((op.instance_name, op) for op in self.op.instances)
1936
    (allocatable, failed) = self.ia_result
1937

    
1938
    jobs = []
1939
    for (name, nodes) in allocatable:
1940
      op = op2inst.pop(name)
1941

    
1942
      if len(nodes) > 1:
1943
        (op.pnode, op.snode) = nodes
1944
      else:
1945
        (op.pnode,) = nodes
1946

    
1947
      jobs.append([op])
1948

    
1949
    missing = set(op2inst.keys()) - set(failed)
1950
    assert not missing, \
1951
      "Iallocator did return incomplete result: %s" % utils.CommaJoin(missing)
1952

    
1953
    return ResultWithJobs(jobs, **self._ConstructPartialResult())
1954

    
1955

    
1956
class _InstNicModPrivate:
1957
  """Data structure for network interface modifications.
1958

1959
  Used by L{LUInstanceSetParams}.
1960

1961
  """
1962
  def __init__(self):
1963
    self.params = None
1964
    self.filled = None
1965

    
1966

    
1967
def PrepareContainerMods(mods, private_fn):
1968
  """Prepares a list of container modifications by adding a private data field.
1969

1970
  @type mods: list of tuples; (operation, index, parameters)
1971
  @param mods: List of modifications
1972
  @type private_fn: callable or None
1973
  @param private_fn: Callable for constructing a private data field for a
1974
    modification
1975
  @rtype: list
1976

1977
  """
1978
  if private_fn is None:
1979
    fn = lambda: None
1980
  else:
1981
    fn = private_fn
1982

    
1983
  return [(op, idx, params, fn()) for (op, idx, params) in mods]
1984

    
1985

    
1986
def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
1987
  """Checks if nodes have enough physical CPUs
1988

1989
  This function checks if all given nodes have the needed number of
1990
  physical CPUs. In case any node has less CPUs or we cannot get the
1991
  information from the node, this function raises an OpPrereqError
1992
  exception.
1993

1994
  @type lu: C{LogicalUnit}
1995
  @param lu: a logical unit from which we get configuration data
1996
  @type nodenames: C{list}
1997
  @param nodenames: the list of node names to check
1998
  @type requested: C{int}
1999
  @param requested: the minimum acceptable number of physical CPUs
2000
  @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
2001
      or we cannot check the node
2002

2003
  """
2004
  nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name], None)
2005
  for node in nodenames:
2006
    info = nodeinfo[node]
2007
    info.Raise("Cannot get current information from node %s" % node,
2008
               prereq=True, ecode=errors.ECODE_ENVIRON)
2009
    (_, _, (hv_info, )) = info.payload
2010
    num_cpus = hv_info.get("cpu_total", None)
2011
    if not isinstance(num_cpus, int):
2012
      raise errors.OpPrereqError("Can't compute the number of physical CPUs"
2013
                                 " on node %s, result was '%s'" %
2014
                                 (node, num_cpus), errors.ECODE_ENVIRON)
2015
    if requested > num_cpus:
2016
      raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
2017
                                 "required" % (node, num_cpus, requested),
2018
                                 errors.ECODE_NORES)
2019

    
2020

    
2021
def GetItemFromContainer(identifier, kind, container):
2022
  """Return the item refered by the identifier.
2023

2024
  @type identifier: string
2025
  @param identifier: Item index or name or UUID
2026
  @type kind: string
2027
  @param kind: One-word item description
2028
  @type container: list
2029
  @param container: Container to get the item from
2030

2031
  """
2032
  # Index
2033
  try:
2034
    idx = int(identifier)
2035
    if idx == -1:
2036
      # Append
2037
      absidx = len(container) - 1
2038
    elif idx < 0:
2039
      raise IndexError("Not accepting negative indices other than -1")
2040
    elif idx > len(container):
2041
      raise IndexError("Got %s index %s, but there are only %s" %
2042
                       (kind, idx, len(container)))
2043
    else:
2044
      absidx = idx
2045
    return (absidx, container[idx])
2046
  except ValueError:
2047
    pass
2048

    
2049
  for idx, item in enumerate(container):
2050
    if item.uuid == identifier or item.name == identifier:
2051
      return (idx, item)
2052

    
2053
  raise errors.OpPrereqError("Cannot find %s with identifier %s" %
2054
                             (kind, identifier), errors.ECODE_NOENT)
2055

    
2056

    
2057
def ApplyContainerMods(kind, container, chgdesc, mods,
2058
                       create_fn, modify_fn, remove_fn):
2059
  """Applies descriptions in C{mods} to C{container}.
2060

2061
  @type kind: string
2062
  @param kind: One-word item description
2063
  @type container: list
2064
  @param container: Container to modify
2065
  @type chgdesc: None or list
2066
  @param chgdesc: List of applied changes
2067
  @type mods: list
2068
  @param mods: Modifications as returned by L{PrepareContainerMods}
2069
  @type create_fn: callable
2070
  @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
2071
    receives absolute item index, parameters and private data object as added
2072
    by L{PrepareContainerMods}, returns tuple containing new item and changes
2073
    as list
2074
  @type modify_fn: callable
2075
  @param modify_fn: Callback for modifying an existing item
2076
    (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
2077
    and private data object as added by L{PrepareContainerMods}, returns
2078
    changes as list
2079
  @type remove_fn: callable
2080
  @param remove_fn: Callback on removing item; receives absolute item index,
2081
    item and private data object as added by L{PrepareContainerMods}
2082

2083
  """
2084
  for (op, identifier, params, private) in mods:
2085
    changes = None
2086

    
2087
    if op == constants.DDM_ADD:
2088
      # Calculate where item will be added
2089
      # When adding an item, identifier can only be an index
2090
      try:
2091
        idx = int(identifier)
2092
      except ValueError:
2093
        raise errors.OpPrereqError("Only possitive integer or -1 is accepted as"
2094
                                   " identifier for %s" % constants.DDM_ADD,
2095
                                   errors.ECODE_INVAL)
2096
      if idx == -1:
2097
        addidx = len(container)
2098
      else:
2099
        if idx < 0:
2100
          raise IndexError("Not accepting negative indices other than -1")
2101
        elif idx > len(container):
2102
          raise IndexError("Got %s index %s, but there are only %s" %
2103
                           (kind, idx, len(container)))
2104
        addidx = idx
2105

    
2106
      if create_fn is None:
2107
        item = params
2108
      else:
2109
        (item, changes) = create_fn(addidx, params, private)
2110

    
2111
      if idx == -1:
2112
        container.append(item)
2113
      else:
2114
        assert idx >= 0
2115
        assert idx <= len(container)
2116
        # list.insert does so before the specified index
2117
        container.insert(idx, item)
2118
    else:
2119
      # Retrieve existing item
2120
      (absidx, item) = GetItemFromContainer(identifier, kind, container)
2121

    
2122
      if op == constants.DDM_REMOVE:
2123
        assert not params
2124

    
2125
        if remove_fn is not None:
2126
          remove_fn(absidx, item, private)
2127

    
2128
        changes = [("%s/%s" % (kind, absidx), "remove")]
2129

    
2130
        assert container[absidx] == item
2131
        del container[absidx]
2132
      elif op == constants.DDM_MODIFY:
2133
        if modify_fn is not None:
2134
          changes = modify_fn(absidx, item, params, private)
2135
      else:
2136
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2137

    
2138
    assert _TApplyContModsCbChanges(changes)
2139

    
2140
    if not (chgdesc is None or changes is None):
2141
      chgdesc.extend(changes)
2142

    
2143

    
2144
def _UpdateIvNames(base_index, disks):
2145
  """Updates the C{iv_name} attribute of disks.
2146

2147
  @type disks: list of L{objects.Disk}
2148

2149
  """
2150
  for (idx, disk) in enumerate(disks):
2151
    disk.iv_name = "disk/%s" % (base_index + idx, )
2152

    
2153

    
2154
class LUInstanceSetParams(LogicalUnit):
2155
  """Modifies an instances's parameters.
2156

2157
  """
2158
  HPATH = "instance-modify"
2159
  HTYPE = constants.HTYPE_INSTANCE
2160
  REQ_BGL = False
2161

    
2162
  @staticmethod
2163
  def _UpgradeDiskNicMods(kind, mods, verify_fn):
2164
    assert ht.TList(mods)
2165
    assert not mods or len(mods[0]) in (2, 3)
2166

    
2167
    if mods and len(mods[0]) == 2:
2168
      result = []
2169

    
2170
      addremove = 0
2171
      for op, params in mods:
2172
        if op in (constants.DDM_ADD, constants.DDM_REMOVE):
2173
          result.append((op, -1, params))
2174
          addremove += 1
2175

    
2176
          if addremove > 1:
2177
            raise errors.OpPrereqError("Only one %s add or remove operation is"
2178
                                       " supported at a time" % kind,
2179
                                       errors.ECODE_INVAL)
2180
        else:
2181
          result.append((constants.DDM_MODIFY, op, params))
2182

    
2183
      assert verify_fn(result)
2184
    else:
2185
      result = mods
2186

    
2187
    return result
2188

    
2189
  @staticmethod
2190
  def _CheckMods(kind, mods, key_types, item_fn):
2191
    """Ensures requested disk/NIC modifications are valid.
2192

2193
    """
2194
    for (op, _, params) in mods:
2195
      assert ht.TDict(params)
2196

    
2197
      # If 'key_types' is an empty dict, we assume we have an
2198
      # 'ext' template and thus do not ForceDictType
2199
      if key_types:
2200
        utils.ForceDictType(params, key_types)
2201

    
2202
      if op == constants.DDM_REMOVE:
2203
        if params:
2204
          raise errors.OpPrereqError("No settings should be passed when"
2205
                                     " removing a %s" % kind,
2206
                                     errors.ECODE_INVAL)
2207
      elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
2208
        item_fn(op, params)
2209
      else:
2210
        raise errors.ProgrammerError("Unhandled operation '%s'" % op)
2211

    
2212
  @staticmethod
2213
  def _VerifyDiskModification(op, params):
2214
    """Verifies a disk modification.
2215

2216
    """
2217
    if op == constants.DDM_ADD:
2218
      mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
2219
      if mode not in constants.DISK_ACCESS_SET:
2220
        raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
2221
                                   errors.ECODE_INVAL)
2222

    
2223
      size = params.get(constants.IDISK_SIZE, None)
2224
      if size is None:
2225
        raise errors.OpPrereqError("Required disk parameter '%s' missing" %
2226
                                   constants.IDISK_SIZE, errors.ECODE_INVAL)
2227

    
2228
      try:
2229
        size = int(size)
2230
      except (TypeError, ValueError), err:
2231
        raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
2232
                                   errors.ECODE_INVAL)
2233

    
2234
      params[constants.IDISK_SIZE] = size
2235
      name = params.get(constants.IDISK_NAME, None)
2236
      if name is not None and name.lower() == constants.VALUE_NONE:
2237
        params[constants.IDISK_NAME] = None
2238

    
2239
    elif op == constants.DDM_MODIFY:
2240
      if constants.IDISK_SIZE in params:
2241
        raise errors.OpPrereqError("Disk size change not possible, use"
2242
                                   " grow-disk", errors.ECODE_INVAL)
2243
      if len(params) > 2:
2244
        raise errors.OpPrereqError("Disk modification doesn't support"
2245
                                   " additional arbitrary parameters",
2246
                                   errors.ECODE_INVAL)
2247
      name = params.get(constants.IDISK_NAME, None)
2248
      if name is not None and name.lower() == constants.VALUE_NONE:
2249
        params[constants.IDISK_NAME] = None
2250

    
2251
  @staticmethod
2252
  def _VerifyNicModification(op, params):
2253
    """Verifies a network interface modification.
2254

2255
    """
2256
    if op in (constants.DDM_ADD, constants.DDM_MODIFY):
2257
      ip = params.get(constants.INIC_IP, None)
2258
      name = params.get(constants.INIC_NAME, None)
2259
      req_net = params.get(constants.INIC_NETWORK, None)
2260
      link = params.get(constants.NIC_LINK, None)
2261
      mode = params.get(constants.NIC_MODE, None)
2262
      if name is not None and name.lower() == constants.VALUE_NONE:
2263
        params[constants.INIC_NAME] = None
2264
      if req_net is not None:
2265
        if req_net.lower() == constants.VALUE_NONE:
2266
          params[constants.INIC_NETWORK] = None
2267
          req_net = None
2268
        elif link is not None or mode is not None:
2269
          raise errors.OpPrereqError("If network is given"
2270
                                     " mode or link should not",
2271
                                     errors.ECODE_INVAL)
2272

    
2273
      if op == constants.DDM_ADD:
2274
        macaddr = params.get(constants.INIC_MAC, None)
2275
        if macaddr is None:
2276
          params[constants.INIC_MAC] = constants.VALUE_AUTO
2277

    
2278
      if ip is not None:
2279
        if ip.lower() == constants.VALUE_NONE:
2280
          params[constants.INIC_IP] = None
2281
        else:
2282
          if ip.lower() == constants.NIC_IP_POOL:
2283
            if op == constants.DDM_ADD and req_net is None:
2284
              raise errors.OpPrereqError("If ip=pool, parameter network"
2285
                                         " cannot be none",
2286
                                         errors.ECODE_INVAL)
2287
          else:
2288
            if not netutils.IPAddress.IsValid(ip):
2289
              raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
2290
                                         errors.ECODE_INVAL)
2291

    
2292
      if constants.INIC_MAC in params:
2293
        macaddr = params[constants.INIC_MAC]
2294
        if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2295
          macaddr = utils.NormalizeAndValidateMac(macaddr)
2296

    
2297
        if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
2298
          raise errors.OpPrereqError("'auto' is not a valid MAC address when"
2299
                                     " modifying an existing NIC",
2300
                                     errors.ECODE_INVAL)
2301

    
2302
  def CheckArguments(self):
2303
    if not (self.op.nics or self.op.disks or self.op.disk_template or
2304
            self.op.hvparams or self.op.beparams or self.op.os_name or
2305
            self.op.offline is not None or self.op.runtime_mem or
2306
            self.op.pnode):
2307
      raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
2308

    
2309
    if self.op.hvparams:
2310
      _CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
2311
                            "hypervisor", "instance", "cluster")
2312

    
2313
    self.op.disks = self._UpgradeDiskNicMods(
2314
      "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
2315
    self.op.nics = self._UpgradeDiskNicMods(
2316
      "NIC", self.op.nics, opcodes.OpInstanceSetParams.TestNicModifications)
2317

    
2318
    if self.op.disks and self.op.disk_template is not None:
2319
      raise errors.OpPrereqError("Disk template conversion and other disk"
2320
                                 " changes not supported at the same time",
2321
                                 errors.ECODE_INVAL)
2322

    
2323
    if (self.op.disk_template and
2324
        self.op.disk_template in constants.DTS_INT_MIRROR and
2325
        self.op.remote_node is None):
2326
      raise errors.OpPrereqError("Changing the disk template to a mirrored"
2327
                                 " one requires specifying a secondary node",
2328
                                 errors.ECODE_INVAL)
2329

    
2330
    # Check NIC modifications
2331
    self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
2332
                    self._VerifyNicModification)
2333

    
2334
    if self.op.pnode:
2335
      self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
2336

    
2337
  def ExpandNames(self):
2338
    self._ExpandAndLockInstance()
2339
    self.needed_locks[locking.LEVEL_NODEGROUP] = []
2340
    # Can't even acquire node locks in shared mode as upcoming changes in
2341
    # Ganeti 2.6 will start to modify the node object on disk conversion
2342
    self.needed_locks[locking.LEVEL_NODE] = []
2343
    self.needed_locks[locking.LEVEL_NODE_RES] = []
2344
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2345
    # Look node group to look up the ipolicy
2346
    self.share_locks[locking.LEVEL_NODEGROUP] = 1
2347

    
2348
  def DeclareLocks(self, level):
2349
    if level == locking.LEVEL_NODEGROUP:
2350
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
2351
      # Acquire locks for the instance's nodegroups optimistically. Needs
2352
      # to be verified in CheckPrereq
2353
      self.needed_locks[locking.LEVEL_NODEGROUP] = \
2354
        self.cfg.GetInstanceNodeGroups(self.op.instance_name)
2355
    elif level == locking.LEVEL_NODE:
2356
      self._LockInstancesNodes()
2357
      if self.op.disk_template and self.op.remote_node:
2358
        self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
2359
        self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
2360
    elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
2361
      # Copy node locks
2362
      self.needed_locks[locking.LEVEL_NODE_RES] = \
2363
        _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
2364

    
2365
  def BuildHooksEnv(self):
2366
    """Build hooks env.
2367

2368
    This runs on the master, primary and secondaries.
2369

2370
    """
2371
    args = {}
2372
    if constants.BE_MINMEM in self.be_new:
2373
      args["minmem"] = self.be_new[constants.BE_MINMEM]
2374
    if constants.BE_MAXMEM in self.be_new:
2375
      args["maxmem"] = self.be_new[constants.BE_MAXMEM]
2376
    if constants.BE_VCPUS in self.be_new:
2377
      args["vcpus"] = self.be_new[constants.BE_VCPUS]
2378
    # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
2379
    # information at all.
2380

    
2381
    if self._new_nics is not None:
2382
      nics = []
2383

    
2384
      for nic in self._new_nics:
2385
        n = copy.deepcopy(nic)
2386
        nicparams = self.cluster.SimpleFillNIC(n.nicparams)
2387
        n.nicparams = nicparams
2388
        nics.append(_NICToTuple(self, n))
2389

    
2390
      args["nics"] = nics
2391

    
2392
    env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
2393
    if self.op.disk_template:
2394
      env["NEW_DISK_TEMPLATE"] = self.op.disk_template
2395
    if self.op.runtime_mem:
2396
      env["RUNTIME_MEMORY"] = self.op.runtime_mem
2397

    
2398
    return env
2399

    
2400
  def BuildHooksNodes(self):
2401
    """Build hooks nodes.
2402

2403
    """
2404
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
2405
    return (nl, nl)
2406

    
2407
  def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
2408
                              old_params, cluster, pnode):
2409

    
2410
    update_params_dict = dict([(key, params[key])
2411
                               for key in constants.NICS_PARAMETERS
2412
                               if key in params])
2413

    
2414
    req_link = update_params_dict.get(constants.NIC_LINK, None)
2415
    req_mode = update_params_dict.get(constants.NIC_MODE, None)
2416

    
2417
    new_net_uuid = None
2418
    new_net_uuid_or_name = params.get(constants.INIC_NETWORK, old_net_uuid)
2419
    if new_net_uuid_or_name:
2420
      new_net_uuid = self.cfg.LookupNetwork(new_net_uuid_or_name)
2421
      new_net_obj = self.cfg.GetNetwork(new_net_uuid)
2422

    
2423
    if old_net_uuid:
2424
      old_net_obj = self.cfg.GetNetwork(old_net_uuid)
2425

    
2426
    if new_net_uuid:
2427
      netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode)
2428
      if not netparams:
2429
        raise errors.OpPrereqError("No netparams found for the network"
2430
                                   " %s, probably not connected" %
2431
                                   new_net_obj.name, errors.ECODE_INVAL)
2432
      new_params = dict(netparams)
2433
    else:
2434
      new_params = _GetUpdatedParams(old_params, update_params_dict)
2435

    
2436
    utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
2437

    
2438
    new_filled_params = cluster.SimpleFillNIC(new_params)
2439
    objects.NIC.CheckParameterSyntax(new_filled_params)
2440

    
2441
    new_mode = new_filled_params[constants.NIC_MODE]
2442
    if new_mode == constants.NIC_MODE_BRIDGED:
2443
      bridge = new_filled_params[constants.NIC_LINK]
2444
      msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
2445
      if msg:
2446
        msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
2447
        if self.op.force:
2448
          self.warn.append(msg)
2449
        else:
2450
          raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
2451

    
2452
    elif new_mode == constants.NIC_MODE_ROUTED:
2453
      ip = params.get(constants.INIC_IP, old_ip)
2454
      if ip is None:
2455
        raise errors.OpPrereqError("Cannot set the NIC IP address to None"
2456
                                   " on a routed NIC", errors.ECODE_INVAL)
2457

    
2458
    elif new_mode == constants.NIC_MODE_OVS:
2459
      # TODO: check OVS link
2460
      self.LogInfo("OVS links are currently not checked for correctness")
2461

    
2462
    if constants.INIC_MAC in params:
2463
      mac = params[constants.INIC_MAC]
2464
      if mac is None:
2465
        raise errors.OpPrereqError("Cannot unset the NIC MAC address",
2466
                                   errors.ECODE_INVAL)
2467
      elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
2468
        # otherwise generate the MAC address
2469
        params[constants.INIC_MAC] = \
2470
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2471
      else:
2472
        # or validate/reserve the current one
2473
        try:
2474
          self.cfg.ReserveMAC(mac, self.proc.GetECId())
2475
        except errors.ReservationError:
2476
          raise errors.OpPrereqError("MAC address '%s' already in use"
2477
                                     " in cluster" % mac,
2478
                                     errors.ECODE_NOTUNIQUE)
2479
    elif new_net_uuid != old_net_uuid:
2480

    
2481
      def get_net_prefix(net_uuid):
2482
        mac_prefix = None
2483
        if net_uuid:
2484
          nobj = self.cfg.GetNetwork(net_uuid)
2485
          mac_prefix = nobj.mac_prefix
2486

    
2487
        return mac_prefix
2488

    
2489
      new_prefix = get_net_prefix(new_net_uuid)
2490
      old_prefix = get_net_prefix(old_net_uuid)
2491
      if old_prefix != new_prefix:
2492
        params[constants.INIC_MAC] = \
2493
          self.cfg.GenerateMAC(new_net_uuid, self.proc.GetECId())
2494

    
2495
    # if there is a change in (ip, network) tuple
2496
    new_ip = params.get(constants.INIC_IP, old_ip)
2497
    if (new_ip, new_net_uuid) != (old_ip, old_net_uuid):
2498
      if new_ip:
2499
        # if IP is pool then require a network and generate one IP
2500
        if new_ip.lower() == constants.NIC_IP_POOL:
2501
          if new_net_uuid:
2502
            try:
2503
              new_ip = self.cfg.GenerateIp(new_net_uuid, self.proc.GetECId())
2504
            except errors.ReservationError:
2505
              raise errors.OpPrereqError("Unable to get a free IP"
2506
                                         " from the address pool",
2507
                                         errors.ECODE_STATE)
2508
            self.LogInfo("Chose IP %s from network %s",
2509
                         new_ip,
2510
                         new_net_obj.name)
2511
            params[constants.INIC_IP] = new_ip
2512
          else:
2513
            raise errors.OpPrereqError("ip=pool, but no network found",
2514
                                       errors.ECODE_INVAL)
2515
        # Reserve new IP if in the new network if any
2516
        elif new_net_uuid:
2517
          try:
2518
            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId())
2519
            self.LogInfo("Reserving IP %s in network %s",
2520
                         new_ip, new_net_obj.name)
2521
          except errors.ReservationError:
2522
            raise errors.OpPrereqError("IP %s not available in network %s" %
2523
                                       (new_ip, new_net_obj.name),
2524
                                       errors.ECODE_NOTUNIQUE)
2525
        # new network is None so check if new IP is a conflicting IP
2526
        elif self.op.conflicts_check:
2527
          _CheckForConflictingIp(self, new_ip, pnode)
2528

    
2529
      # release old IP if old network is not None
2530
      if old_ip and old_net_uuid:
2531
        try:
2532
          self.cfg.ReleaseIp(old_net_uuid, old_ip, self.proc.GetECId())
2533
        except errors.AddressPoolError:
2534
          logging.warning("Release IP %s not contained in network %s",
2535
                          old_ip, old_net_obj.name)
2536

    
2537
    # there are no changes in (ip, network) tuple and old network is not None
2538
    elif (old_net_uuid is not None and
2539
          (req_link is not None or req_mode is not None)):
2540
      raise errors.OpPrereqError("Not allowed to change link or mode of"
2541
                                 " a NIC that is connected to a network",
2542
                                 errors.ECODE_INVAL)
2543

    
2544
    private.params = new_params
2545
    private.filled = new_filled_params
2546

    
2547
  def _PreCheckDiskTemplate(self, pnode_info):
2548
    """CheckPrereq checks related to a new disk template."""
2549
    # Arguments are passed to avoid configuration lookups
2550
    instance = self.instance
2551
    pnode = instance.primary_node
2552
    cluster = self.cluster
2553
    if instance.disk_template == self.op.disk_template:
2554
      raise errors.OpPrereqError("Instance already has disk template %s" %
2555
                                 instance.disk_template, errors.ECODE_INVAL)
2556

    
2557
    if (instance.disk_template,
2558
        self.op.disk_template) not in self._DISK_CONVERSIONS:
2559
      raise errors.OpPrereqError("Unsupported disk template conversion from"
2560
                                 " %s to %s" % (instance.disk_template,
2561
                                                self.op.disk_template),
2562
                                 errors.ECODE_INVAL)
2563
    _CheckInstanceState(self, instance, INSTANCE_DOWN,
2564
                        msg="cannot change disk template")
2565
    if self.op.disk_template in constants.DTS_INT_MIRROR:
2566
      if self.op.remote_node == pnode:
2567
        raise errors.OpPrereqError("Given new secondary node %s is the same"
2568
                                   " as the primary node of the instance" %
2569
                                   self.op.remote_node, errors.ECODE_STATE)
2570
      _CheckNodeOnline(self, self.op.remote_node)
2571
      _CheckNodeNotDrained(self, self.op.remote_node)
2572
      # FIXME: here we assume that the old instance type is DT_PLAIN
2573
      assert instance.disk_template == constants.DT_PLAIN
2574
      disks = [{constants.IDISK_SIZE: d.size,
2575
                constants.IDISK_VG: d.logical_id[0]}
2576
               for d in instance.disks]
2577
      required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
2578
      _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
2579

    
2580
      snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
2581
      snode_group = self.cfg.GetNodeGroup(snode_info.group)
2582
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2583
                                                              snode_group)
2584
      _CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info, self.cfg,
2585
                              ignore=self.op.ignore_ipolicy)
2586
      if pnode_info.group != snode_info.group:
2587
        self.LogWarning("The primary and secondary nodes are in two"
2588
                        " different node groups; the disk parameters"
2589
                        " from the first disk's node group will be"
2590
                        " used")
2591

    
2592
    if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
2593
      # Make sure none of the nodes require exclusive storage
2594
      nodes = [pnode_info]
2595
      if self.op.disk_template in constants.DTS_INT_MIRROR:
2596
        assert snode_info
2597
        nodes.append(snode_info)
2598
      has_es = lambda n: _IsExclusiveStorageEnabledNode(self.cfg, n)
2599
      if compat.any(map(has_es, nodes)):
2600
        errmsg = ("Cannot convert disk template from %s to %s when exclusive"
2601
                  " storage is enabled" % (instance.disk_template,
2602
                                           self.op.disk_template))
2603
        raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
2604

    
2605
  def CheckPrereq(self):
2606
    """Check prerequisites.
2607

2608
    This only checks the instance list against the existing names.
2609

2610
    """
2611
    assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
2612
    instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
2613

    
2614
    cluster = self.cluster = self.cfg.GetClusterInfo()
2615
    assert self.instance is not None, \
2616
      "Cannot retrieve locked instance %s" % self.op.instance_name
2617

    
2618
    pnode = instance.primary_node
2619

    
2620
    self.warn = []
2621

    
2622
    if (self.op.pnode is not None and self.op.pnode != pnode and
2623
        not self.op.force):
2624
      # verify that the instance is not up
2625
      instance_info = self.rpc.call_instance_info(pnode, instance.name,
2626
                                                  instance.hypervisor)
2627
      if instance_info.fail_msg:
2628
        self.warn.append("Can't get instance runtime information: %s" %
2629
                         instance_info.fail_msg)
2630
      elif instance_info.payload:
2631
        raise errors.OpPrereqError("Instance is still running on %s" % pnode,
2632
                                   errors.ECODE_STATE)
2633

    
2634
    assert pnode in self.owned_locks(locking.LEVEL_NODE)
2635
    nodelist = list(instance.all_nodes)
2636
    pnode_info = self.cfg.GetNodeInfo(pnode)
2637
    self.diskparams = self.cfg.GetInstanceDiskParams(instance)
2638

    
2639
    #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
2640
    assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
2641
    group_info = self.cfg.GetNodeGroup(pnode_info.group)
2642

    
2643
    # dictionary with instance information after the modification
2644
    ispec = {}
2645

    
2646
    # Check disk modifications. This is done here and not in CheckArguments
2647
    # (as with NICs), because we need to know the instance's disk template
2648
    if instance.disk_template == constants.DT_EXT:
2649
      self._CheckMods("disk", self.op.disks, {},
2650
                      self._VerifyDiskModification)
2651
    else:
2652
      self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
2653
                      self._VerifyDiskModification)
2654

    
2655
    # Prepare disk/NIC modifications
2656
    self.diskmod = PrepareContainerMods(self.op.disks, None)
2657
    self.nicmod = PrepareContainerMods(self.op.nics, _InstNicModPrivate)
2658

    
2659
    # Check the validity of the `provider' parameter
2660
    if instance.disk_template in constants.DT_EXT:
2661
      for mod in self.diskmod:
2662
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2663
        if mod[0] == constants.DDM_ADD:
2664
          if ext_provider is None:
2665
            raise errors.OpPrereqError("Instance template is '%s' and parameter"
2666
                                       " '%s' missing, during disk add" %
2667
                                       (constants.DT_EXT,
2668
                                        constants.IDISK_PROVIDER),
2669
                                       errors.ECODE_NOENT)
2670
        elif mod[0] == constants.DDM_MODIFY:
2671
          if ext_provider:
2672
            raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
2673
                                       " modification" %
2674
                                       constants.IDISK_PROVIDER,
2675
                                       errors.ECODE_INVAL)
2676
    else:
2677
      for mod in self.diskmod:
2678
        ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
2679
        if ext_provider is not None:
2680
          raise errors.OpPrereqError("Parameter '%s' is only valid for"
2681
                                     " instances of type '%s'" %
2682
                                     (constants.IDISK_PROVIDER,
2683
                                      constants.DT_EXT),
2684
                                     errors.ECODE_INVAL)
2685

    
2686
    # OS change
2687
    if self.op.os_name and not self.op.force:
2688
      _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
2689
                      self.op.force_variant)
2690
      instance_os = self.op.os_name
2691
    else:
2692
      instance_os = instance.os
2693

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

    
2697
    if self.op.disk_template:
2698
      self._PreCheckDiskTemplate(pnode_info)
2699

    
2700
    # hvparams processing
2701
    if self.op.hvparams:
2702
      hv_type = instance.hypervisor
2703
      i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
2704
      utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
2705
      hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
2706

    
2707
      # local check
2708
      hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
2709
      _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
2710
      self.hv_proposed = self.hv_new = hv_new # the new actual values
2711
      self.hv_inst = i_hvdict # the new dict (without defaults)
2712
    else:
2713
      self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
2714
                                              instance.hvparams)
2715
      self.hv_new = self.hv_inst = {}
2716

    
2717
    # beparams processing
2718
    if self.op.beparams:
2719
      i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
2720
                                   use_none=True)
2721
      objects.UpgradeBeParams(i_bedict)
2722
      utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
2723
      be_new = cluster.SimpleFillBE(i_bedict)
2724
      self.be_proposed = self.be_new = be_new # the new actual values
2725
      self.be_inst = i_bedict # the new dict (without defaults)
2726
    else:
2727
      self.be_new = self.be_inst = {}
2728
      self.be_proposed = cluster.SimpleFillBE(instance.beparams)
2729
    be_old = cluster.FillBE(instance)
2730

    
2731
    # CPU param validation -- checking every time a parameter is
2732
    # changed to cover all cases where either CPU mask or vcpus have
2733
    # changed
2734
    if (constants.BE_VCPUS in self.be_proposed and
2735
        constants.HV_CPU_MASK in self.hv_proposed):
2736
      cpu_list = \
2737
        utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
2738
      # Verify mask is consistent with number of vCPUs. Can skip this
2739
      # test if only 1 entry in the CPU mask, which means same mask
2740
      # is applied to all vCPUs.
2741
      if (len(cpu_list) > 1 and
2742
          len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
2743
        raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
2744
                                   " CPU mask [%s]" %
2745
                                   (self.be_proposed[constants.BE_VCPUS],
2746
                                    self.hv_proposed[constants.HV_CPU_MASK]),
2747
                                   errors.ECODE_INVAL)
2748

    
2749
      # Only perform this test if a new CPU mask is given
2750
      if constants.HV_CPU_MASK in self.hv_new:
2751
        # Calculate the largest CPU number requested
2752
        max_requested_cpu = max(map(max, cpu_list))
2753
        # Check that all of the instance's nodes have enough physical CPUs to
2754
        # satisfy the requested CPU mask
2755
        _CheckNodesPhysicalCPUs(self, instance.all_nodes,
2756
                                max_requested_cpu + 1, instance.hypervisor)
2757

    
2758
    # osparams processing
2759
    if self.op.osparams:
2760
      i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
2761
      _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
2762
      self.os_inst = i_osdict # the new dict (without defaults)
2763
    else:
2764
      self.os_inst = {}
2765

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

    
2809
      if be_new[constants.BE_AUTO_BALANCE]:
2810
        for node, nres in nodeinfo.items():
2811
          if node not in instance.secondary_nodes:
2812
            continue
2813
          nres.Raise("Can't get info from secondary node %s" % node,
2814
                     prereq=True, ecode=errors.ECODE_STATE)
2815
          (_, _, (nhvinfo, )) = nres.payload
2816
          if not isinstance(nhvinfo.get("memory_free", None), int):
2817
            raise errors.OpPrereqError("Secondary node %s didn't return free"
2818
                                       " memory information" % node,
2819
                                       errors.ECODE_STATE)
2820
          #TODO(dynmem): do the appropriate check involving MINMEM
2821
          elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
2822
            raise errors.OpPrereqError("This change will prevent the instance"
2823
                                       " from failover to its secondary node"
2824
                                       " %s, due to not enough memory" % node,
2825
                                       errors.ECODE_STATE)
2826

    
2827
    if self.op.runtime_mem:
2828
      remote_info = self.rpc.call_instance_info(instance.primary_node,
2829
                                                instance.name,
2830
                                                instance.hypervisor)
2831
      remote_info.Raise("Error checking node %s" % instance.primary_node)
2832
      if not remote_info.payload: # not running already
2833
        raise errors.OpPrereqError("Instance %s is not running" %
2834
                                   instance.name, errors.ECODE_STATE)
2835

    
2836
      current_memory = remote_info.payload["memory"]
2837
      if (not self.op.force and
2838
           (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
2839
            self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
2840
        raise errors.OpPrereqError("Instance %s must have memory between %d"
2841
                                   " and %d MB of memory unless --force is"
2842
                                   " given" %
2843
                                   (instance.name,
2844
                                    self.be_proposed[constants.BE_MINMEM],
2845
                                    self.be_proposed[constants.BE_MAXMEM]),
2846
                                   errors.ECODE_INVAL)
2847

    
2848
      delta = self.op.runtime_mem - current_memory
2849
      if delta > 0:
2850
        _CheckNodeFreeMemory(self, instance.primary_node,
2851
                             "ballooning memory for instance %s" %
2852
                             instance.name, delta, instance.hypervisor)
2853

    
2854
    if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
2855
      raise errors.OpPrereqError("Disk operations not supported for"
2856
                                 " diskless instances", errors.ECODE_INVAL)
2857

    
2858
    def _PrepareNicCreate(_, params, private):
2859
      self._PrepareNicModification(params, private, None, None,
2860
                                   {}, cluster, pnode)
2861
      return (None, None)
2862

    
2863
    def _PrepareNicMod(_, nic, params, private):
2864
      self._PrepareNicModification(params, private, nic.ip, nic.network,
2865
                                   nic.nicparams, cluster, pnode)
2866
      return None
2867

    
2868
    def _PrepareNicRemove(_, params, __):
2869
      ip = params.ip
2870
      net = params.network
2871
      if net is not None and ip is not None:
2872
        self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
2873

    
2874
    # Verify NIC changes (operating on copy)
2875
    nics = instance.nics[:]
2876
    ApplyContainerMods("NIC", nics, None, self.nicmod,
2877
                       _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
2878
    if len(nics) > constants.MAX_NICS:
2879
      raise errors.OpPrereqError("Instance has too many network interfaces"
2880
                                 " (%d), cannot add more" % constants.MAX_NICS,
2881
                                 errors.ECODE_STATE)
2882

    
2883
    def _PrepareDiskMod(_, disk, params, __):
2884
      disk.name = params.get(constants.IDISK_NAME, None)
2885

    
2886
    # Verify disk changes (operating on a copy)
2887
    disks = copy.deepcopy(instance.disks)
2888
    ApplyContainerMods("disk", disks, None, self.diskmod, None, _PrepareDiskMod,
2889
                       None)
2890
    utils.ValidateDeviceNames("disk", disks)
2891
    if len(disks) > constants.MAX_DISKS:
2892
      raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
2893
                                 " more" % constants.MAX_DISKS,
2894
                                 errors.ECODE_STATE)
2895
    disk_sizes = [disk.size for disk in instance.disks]
2896
    disk_sizes.extend(params["size"] for (op, idx, params, private) in
2897
                      self.diskmod if op == constants.DDM_ADD)
2898
    ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
2899
    ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
2900

    
2901
    if self.op.offline is not None and self.op.offline:
2902
      _CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE,
2903
                          msg="can't change to offline")
2904

    
2905
    # Pre-compute NIC changes (necessary to use result in hooks)
2906
    self._nic_chgdesc = []
2907
    if self.nicmod:
2908
      # Operate on copies as this is still in prereq
2909
      nics = [nic.Copy() for nic in instance.nics]
2910
      ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
2911
                         self._CreateNewNic, self._ApplyNicMods, None)
2912
      # Verify that NIC names are unique and valid
2913
      utils.ValidateDeviceNames("NIC", nics)
2914
      self._new_nics = nics
2915
      ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
2916
    else:
2917
      self._new_nics = None
2918
      ispec[constants.ISPEC_NIC_COUNT] = len(instance.nics)
2919

    
2920
    if not self.op.ignore_ipolicy:
2921
      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2922
                                                              group_info)
2923

    
2924
      # Fill ispec with backend parameters
2925
      ispec[constants.ISPEC_SPINDLE_USE] = \
2926
        self.be_new.get(constants.BE_SPINDLE_USE, None)
2927
      ispec[constants.ISPEC_CPU_COUNT] = self.be_new.get(constants.BE_VCPUS,
2928
                                                         None)
2929

    
2930
      # Copy ispec to verify parameters with min/max values separately
2931
      if self.op.disk_template:
2932
        new_disk_template = self.op.disk_template
2933
      else:
2934
        new_disk_template = instance.disk_template
2935
      ispec_max = ispec.copy()
2936
      ispec_max[constants.ISPEC_MEM_SIZE] = \
2937
        self.be_new.get(constants.BE_MAXMEM, None)
2938
      res_max = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_max,
2939
                                                     new_disk_template)
2940
      ispec_min = ispec.copy()
2941
      ispec_min[constants.ISPEC_MEM_SIZE] = \
2942
        self.be_new.get(constants.BE_MINMEM, None)
2943
      res_min = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
2944
                                                     new_disk_template)
2945

    
2946
      if (res_max or res_min):
2947
        # FIXME: Improve error message by including information about whether
2948
        # the upper or lower limit of the parameter fails the ipolicy.
2949
        msg = ("Instance allocation to group %s (%s) violates policy: %s" %
2950
               (group_info, group_info.name,
2951
                utils.CommaJoin(set(res_max + res_min))))
2952
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2953

    
2954
  def _ConvertPlainToDrbd(self, feedback_fn):
2955
    """Converts an instance from plain to drbd.
2956

2957
    """
2958
    feedback_fn("Converting template to drbd")
2959
    instance = self.instance
2960
    pnode = instance.primary_node
2961
    snode = self.op.remote_node
2962

    
2963
    assert instance.disk_template == constants.DT_PLAIN
2964

    
2965
    # create a fake disk info for _GenerateDiskTemplate
2966
    disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
2967
                  constants.IDISK_VG: d.logical_id[0],
2968
                  constants.IDISK_NAME: d.name}
2969
                 for d in instance.disks]
2970
    new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
2971
                                      instance.name, pnode, [snode],
2972
                                      disk_info, None, None, 0, feedback_fn,
2973
                                      self.diskparams)
2974
    anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
2975
                                        self.diskparams)
2976
    p_excl_stor = _IsExclusiveStorageEnabledNodeName(self.cfg, pnode)
2977
    s_excl_stor = _IsExclusiveStorageEnabledNodeName(self.cfg, snode)
2978
    info = _GetInstanceInfoText(instance)
2979
    feedback_fn("Creating additional volumes...")
2980
    # first, create the missing data and meta devices
2981
    for disk in anno_disks:
2982
      # unfortunately this is... not too nice
2983
      _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
2984
                            info, True, p_excl_stor)
2985
      for child in disk.children:
2986
        _CreateSingleBlockDev(self, snode, instance, child, info, True,
2987
                              s_excl_stor)
2988
    # at this stage, all new LVs have been created, we can rename the
2989
    # old ones
2990
    feedback_fn("Renaming original volumes...")
2991
    rename_list = [(o, n.children[0].logical_id)
2992
                   for (o, n) in zip(instance.disks, new_disks)]
2993
    result = self.rpc.call_blockdev_rename(pnode, rename_list)
2994
    result.Raise("Failed to rename original LVs")
2995

    
2996
    feedback_fn("Initializing DRBD devices...")
2997
    # all child devices are in place, we can now create the DRBD devices
2998
    try:
2999
      for disk in anno_disks:
3000
        for (node, excl_stor) in [(pnode, p_excl_stor), (snode, s_excl_stor)]:
3001
          f_create = node == pnode
3002
          _CreateSingleBlockDev(self, node, instance, disk, info, f_create,
3003
                                excl_stor)
3004
    except errors.GenericError, e:
3005
      feedback_fn("Initializing of DRBD devices failed;"
3006
                  " renaming back original volumes...")
3007
      for disk in new_disks:
3008
        self.cfg.SetDiskID(disk, pnode)
3009
      rename_back_list = [(n.children[0], o.logical_id)
3010
                          for (n, o) in zip(new_disks, instance.disks)]
3011
      result = self.rpc.call_blockdev_rename(pnode, rename_back_list)
3012
      result.Raise("Failed to rename LVs back after error %s" % str(e))
3013
      raise
3014

    
3015
    # at this point, the instance has been modified
3016
    instance.disk_template = constants.DT_DRBD8
3017
    instance.disks = new_disks
3018
    self.cfg.Update(instance, feedback_fn)
3019

    
3020
    # Release node locks while waiting for sync
3021
    _ReleaseLocks(self, locking.LEVEL_NODE)
3022

    
3023
    # disks are created, waiting for sync
3024
    disk_abort = not _WaitForSync(self, instance,
3025
                                  oneshot=not self.op.wait_for_sync)
3026
    if disk_abort:
3027
      raise errors.OpExecError("There are some degraded disks for"
3028
                               " this instance, please cleanup manually")
3029

    
3030
    # Node resource locks will be released by caller
3031

    
3032
  def _ConvertDrbdToPlain(self, feedback_fn):
3033
    """Converts an instance from drbd to plain.
3034

3035
    """
3036
    instance = self.instance
3037

    
3038
    assert len(instance.secondary_nodes) == 1
3039
    assert instance.disk_template == constants.DT_DRBD8
3040

    
3041
    pnode = instance.primary_node
3042
    snode = instance.secondary_nodes[0]
3043
    feedback_fn("Converting template to plain")
3044

    
3045
    old_disks = _AnnotateDiskParams(instance, instance.disks, self.cfg)
3046
    new_disks = [d.children[0] for d in instance.disks]
3047

    
3048
    # copy over size, mode and name
3049
    for parent, child in zip(old_disks, new_disks):
3050
      child.size = parent.size
3051
      child.mode = parent.mode
3052
      child.name = parent.name
3053

    
3054
    # this is a DRBD disk, return its port to the pool
3055
    # NOTE: this must be done right before the call to cfg.Update!
3056
    for disk in old_disks:
3057
      tcp_port = disk.logical_id[2]
3058
      self.cfg.AddTcpUdpPort(tcp_port)
3059

    
3060
    # update instance structure
3061
    instance.disks = new_disks
3062
    instance.disk_template = constants.DT_PLAIN
3063
    _UpdateIvNames(0, instance.disks)
3064
    self.cfg.Update(instance, feedback_fn)
3065

    
3066
    # Release locks in case removing disks takes a while
3067
    _ReleaseLocks(self, locking.LEVEL_NODE)
3068

    
3069
    feedback_fn("Removing volumes on the secondary node...")
3070
    for disk in old_disks:
3071
      self.cfg.SetDiskID(disk, snode)
3072
      msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
3073
      if msg:
3074
        self.LogWarning("Could not remove block device %s on node %s,"
3075
                        " continuing anyway: %s", disk.iv_name, snode, msg)
3076

    
3077
    feedback_fn("Removing unneeded volumes on the primary node...")
3078
    for idx, disk in enumerate(old_disks):
3079
      meta = disk.children[1]
3080
      self.cfg.SetDiskID(meta, pnode)
3081
      msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
3082
      if msg:
3083
        self.LogWarning("Could not remove metadata for disk %d on node %s,"
3084
                        " continuing anyway: %s", idx, pnode, msg)
3085

    
3086
  def _CreateNewDisk(self, idx, params, _):
3087
    """Creates a new disk.
3088

3089
    """
3090
    instance = self.instance
3091

    
3092
    # add a new disk
3093
    if instance.disk_template in constants.DTS_FILEBASED:
3094
      (file_driver, file_path) = instance.disks[0].logical_id
3095
      file_path = os.path.dirname(file_path)
3096
    else:
3097
      file_driver = file_path = None
3098

    
3099
    disk = \
3100
      _GenerateDiskTemplate(self, instance.disk_template, instance.name,
3101
                            instance.primary_node, instance.secondary_nodes,
3102
                            [params], file_path, file_driver, idx,
3103
                            self.Log, self.diskparams)[0]
3104

    
3105
    info = _GetInstanceInfoText(instance)
3106

    
3107
    logging.info("Creating volume %s for instance %s",
3108
                 disk.iv_name, instance.name)
3109
    # Note: this needs to be kept in sync with _CreateDisks
3110
    #HARDCODE
3111
    for node in instance.all_nodes:
3112
      f_create = (node == instance.primary_node)
3113
      try:
3114
        _CreateBlockDev(self, node, instance, disk, f_create, info, f_create)
3115
      except errors.OpExecError, err:
3116
        self.LogWarning("Failed to create volume %s (%s) on node '%s': %s",
3117
                        disk.iv_name, disk, node, err)
3118

    
3119
    if self.cluster.prealloc_wipe_disks:
3120
      # Wipe new disk
3121
      _WipeDisks(self, instance,
3122
                 disks=[(idx, disk, 0)])
3123

    
3124
    return (disk, [
3125
      ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
3126
      ])
3127

    
3128
  @staticmethod
3129
  def _ModifyDisk(idx, disk, params, _):
3130
    """Modifies a disk.
3131

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

    
3139
    name = params.get(constants.IDISK_NAME, None)
3140
    disk.name = name
3141
    changes.append(("disk.name/%d" % idx, disk.name))
3142

    
3143
    return changes
3144

    
3145
  def _RemoveDisk(self, idx, root, _):
3146
    """Removes a disk.
3147

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

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

    
3161
  def _CreateNewNic(self, idx, params, private):
3162
    """Creates data structure for a new network interface.
3163

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

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

    
3184
  def _ApplyNicMods(self, idx, nic, params, private):
3185
    """Modifies a network interface.
3186

3187
    """
3188
    changes = []
3189

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

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

    
3201
    if private.filled:
3202
      nic.nicparams = private.filled
3203

    
3204
      for (key, val) in nic.nicparams.items():
3205
        changes.append(("nic.%s/%d" % (key, idx), val))
3206

    
3207
    return changes
3208

    
3209
  def Exec(self, feedback_fn):
3210
    """Modifies an instance.
3211

3212
    All parameters take effect only at the next restart of the instance.
3213

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

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

    
3225
    result = []
3226
    instance = self.instance
3227

    
3228
    # New primary node
3229
    if self.op.pnode:
3230
      instance.primary_node = self.op.pnode
3231

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

    
3240
    # Apply disk changes
3241
    ApplyContainerMods("disk", instance.disks, result, self.diskmod,
3242
                       self._CreateNewDisk, self._ModifyDisk, self._RemoveDisk)
3243
    _UpdateIvNames(0, instance.disks)
3244

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

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

    
3268
      assert instance.disk_template == self.op.disk_template, \
3269
        ("Expected disk template '%s', found '%s'" %
3270
         (self.op.disk_template, instance.disk_template))
3271

    
3272
    # Release node and resource locks if there are any (they might already have
3273
    # been released during disk conversion)
3274
    _ReleaseLocks(self, locking.LEVEL_NODE)
3275
    _ReleaseLocks(self, locking.LEVEL_NODE_RES)
3276

    
3277
    # Apply NIC changes
3278
    if self._new_nics is not None:
3279
      instance.nics = self._new_nics
3280
      result.extend(self._nic_chgdesc)
3281

    
3282
    # hvparams changes
3283
    if self.op.hvparams:
3284
      instance.hvparams = self.hv_inst
3285
      for key, val in self.op.hvparams.iteritems():
3286
        result.append(("hv/%s" % key, val))
3287

    
3288
    # beparams changes
3289
    if self.op.beparams:
3290
      instance.beparams = self.be_inst
3291
      for key, val in self.op.beparams.iteritems():
3292
        result.append(("be/%s" % key, val))
3293

    
3294
    # OS change
3295
    if self.op.os_name:
3296
      instance.os = self.op.os_name
3297

    
3298
    # osparams changes
3299
    if self.op.osparams:
3300
      instance.osparams = self.os_inst
3301
      for key, val in self.op.osparams.iteritems():
3302
        result.append(("os/%s" % key, val))
3303

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

    
3316
    self.cfg.Update(instance, feedback_fn, self.proc.GetECId())
3317

    
3318
    assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
3319
                self.owned_locks(locking.LEVEL_NODE)), \
3320
      "All node locks should have been released by now"
3321

    
3322
    return result
3323

    
3324
  _DISK_CONVERSIONS = {
3325
    (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
3326
    (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
3327
    }
3328

    
3329

    
3330
class LUInstanceChangeGroup(LogicalUnit):
3331
  HPATH = "instance-change-group"
3332
  HTYPE = constants.HTYPE_INSTANCE
3333
  REQ_BGL = False
3334

    
3335
  def ExpandNames(self):
3336
    self.share_locks = _ShareAll()
3337

    
3338
    self.needed_locks = {
3339
      locking.LEVEL_NODEGROUP: [],
3340
      locking.LEVEL_NODE: [],
3341
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
3342
      }
3343

    
3344
    self._ExpandAndLockInstance()
3345

    
3346
    if self.op.target_groups:
3347
      self.req_target_uuids = map(self.cfg.LookupNodeGroup,
3348
                                  self.op.target_groups)
3349
    else:
3350
      self.req_target_uuids = None
3351

    
3352
    self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
3353

    
3354
  def DeclareLocks(self, level):
3355
    if level == locking.LEVEL_NODEGROUP:
3356
      assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3357

    
3358
      if self.req_target_uuids:
3359
        lock_groups = set(self.req_target_uuids)
3360

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

    
3369
      self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
3370

    
3371
    elif level == locking.LEVEL_NODE:
3372
      if self.req_target_uuids:
3373
        # Lock all nodes used by instances
3374
        self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3375
        self._LockInstancesNodes()
3376

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

    
3388
  def CheckPrereq(self):
3389
    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3390
    owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3391
    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3392

    
3393
    assert (self.req_target_uuids is None or
3394
            owned_groups.issuperset(self.req_target_uuids))
3395
    assert owned_instances == set([self.op.instance_name])
3396

    
3397
    # Get instance information
3398
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
3399

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

    
3405
    inst_groups = _CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
3406
                                           owned_groups)
3407

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

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

    
3423
    if not self.target_uuids:
3424
      raise errors.OpPrereqError("There are no possible target groups",
3425
                                 errors.ECODE_INVAL)
3426

    
3427
  def BuildHooksEnv(self):
3428
    """Build hooks env.
3429

3430
    """
3431
    assert self.target_uuids
3432

    
3433
    env = {
3434
      "TARGET_GROUPS": " ".join(self.target_uuids),
3435
      }
3436

    
3437
    env.update(_BuildInstanceHookEnvByObject(self, self.instance))
3438

    
3439
    return env
3440

    
3441
  def BuildHooksNodes(self):
3442
    """Build hooks nodes.
3443

3444
    """
3445
    mn = self.cfg.GetMasterNode()
3446
    return ([mn], [mn])
3447

    
3448
  def Exec(self, feedback_fn):
3449
    instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
3450

    
3451
    assert instances == [self.op.instance_name], "Instance not locked"
3452

    
3453
    req = iallocator.IAReqGroupChange(instances=instances,
3454
                                      target_groups=list(self.target_uuids))
3455
    ial = iallocator.IAllocator(self.cfg, self.rpc, req)
3456

    
3457
    ial.Run(self.op.iallocator)
3458

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

    
3465
    jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
3466

    
3467
    self.LogInfo("Iallocator returned %s job(s) for changing group of"
3468
                 " instance '%s'", len(jobs), self.op.instance_name)
3469

    
3470
    return ResultWithJobs(jobs)