Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_utils.py @ 6ccce5d4

History | View | Annotate | Download (19.2 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
"""Utility function mainly, but not only used by instance LU's."""
23

    
24
import logging
25
import os
26

    
27
from ganeti import constants
28
from ganeti import errors
29
from ganeti import locking
30
from ganeti import network
31
from ganeti import objects
32
from ganeti import pathutils
33
from ganeti import utils
34
from ganeti.cmdlib.common import AnnotateDiskParams, \
35
  ComputeIPolicyInstanceViolation, CheckDiskTemplateEnabled
36

    
37

    
38
def BuildInstanceHookEnv(name, primary_node_name, secondary_node_names, os_type,
39
                         status, minmem, maxmem, vcpus, nics, disk_template,
40
                         disks, bep, hvp, hypervisor_name, tags):
41
  """Builds instance related env variables for hooks
42

43
  This builds the hook environment from individual variables.
44

45
  @type name: string
46
  @param name: the name of the instance
47
  @type primary_node_name: string
48
  @param primary_node_name: the name of the instance's primary node
49
  @type secondary_node_names: list
50
  @param secondary_node_names: list of secondary nodes as strings
51
  @type os_type: string
52
  @param os_type: the name of the instance's OS
53
  @type status: string
54
  @param status: the desired status of the instance
55
  @type minmem: string
56
  @param minmem: the minimum memory size of the instance
57
  @type maxmem: string
58
  @param maxmem: the maximum memory size of the instance
59
  @type vcpus: string
60
  @param vcpus: the count of VCPUs the instance has
61
  @type nics: list
62
  @param nics: list of tuples (name, uuid, ip, mac, mode, link, vlan, net,
63
      netinfo) representing the NICs the instance has
64
  @type disk_template: string
65
  @param disk_template: the disk template of the instance
66
  @type disks: list
67
  @param disks: list of tuples (name, uuid, size, mode)
68
  @type bep: dict
69
  @param bep: the backend parameters for the instance
70
  @type hvp: dict
71
  @param hvp: the hypervisor parameters for the instance
72
  @type hypervisor_name: string
73
  @param hypervisor_name: the hypervisor for the instance
74
  @type tags: list
75
  @param tags: list of instance tags as strings
76
  @rtype: dict
77
  @return: the hook environment for this instance
78

79
  """
80
  env = {
81
    "OP_TARGET": name,
82
    "INSTANCE_NAME": name,
83
    "INSTANCE_PRIMARY": primary_node_name,
84
    "INSTANCE_SECONDARIES": " ".join(secondary_node_names),
85
    "INSTANCE_OS_TYPE": os_type,
86
    "INSTANCE_STATUS": status,
87
    "INSTANCE_MINMEM": minmem,
88
    "INSTANCE_MAXMEM": maxmem,
89
    # TODO(2.9) remove deprecated "memory" value
90
    "INSTANCE_MEMORY": maxmem,
91
    "INSTANCE_VCPUS": vcpus,
92
    "INSTANCE_DISK_TEMPLATE": disk_template,
93
    "INSTANCE_HYPERVISOR": hypervisor_name,
94
    }
95
  if nics:
96
    nic_count = len(nics)
97
    for idx, (name, uuid, ip, mac, mode, link, vlan, net, netinfo) \
98
        in enumerate(nics):
99
      if ip is None:
100
        ip = ""
101
      if name:
102
        env["INSTANCE_NIC%d_NAME" % idx] = name
103
      env["INSTANCE_NIC%d_UUID" % idx] = uuid
104
      env["INSTANCE_NIC%d_IP" % idx] = ip
105
      env["INSTANCE_NIC%d_MAC" % idx] = mac
106
      env["INSTANCE_NIC%d_MODE" % idx] = mode
107
      env["INSTANCE_NIC%d_LINK" % idx] = link
108
      env["INSTANCE_NIC%d_VLAN" % idx] = vlan
109
      if netinfo:
110
        nobj = objects.Network.FromDict(netinfo)
111
        env.update(nobj.HooksDict("INSTANCE_NIC%d_" % idx))
112
      elif network:
113
        # FIXME: broken network reference: the instance NIC specifies a
114
        # network, but the relevant network entry was not in the config. This
115
        # should be made impossible.
116
        env["INSTANCE_NIC%d_NETWORK_NAME" % idx] = net
117
      if mode == constants.NIC_MODE_BRIDGED or \
118
         mode == constants.NIC_MODE_OVS:
119
        env["INSTANCE_NIC%d_BRIDGE" % idx] = link
120
  else:
121
    nic_count = 0
122

    
123
  env["INSTANCE_NIC_COUNT"] = nic_count
124

    
125
  if disks:
126
    disk_count = len(disks)
127
    for idx, (name, uuid, size, mode) in enumerate(disks):
128
      if name:
129
        env["INSTANCE_DISK%d_NAME" % idx] = name
130
      env["INSTANCE_DISK%d_UUID" % idx] = uuid
131
      env["INSTANCE_DISK%d_SIZE" % idx] = size
132
      env["INSTANCE_DISK%d_MODE" % idx] = mode
133
  else:
134
    disk_count = 0
135

    
136
  env["INSTANCE_DISK_COUNT"] = disk_count
137

    
138
  if not tags:
139
    tags = []
140

    
141
  env["INSTANCE_TAGS"] = " ".join(tags)
142

    
143
  for source, kind in [(bep, "BE"), (hvp, "HV")]:
144
    for key, value in source.items():
145
      env["INSTANCE_%s_%s" % (kind, key)] = value
146

    
147
  return env
148

    
149

    
150
def BuildInstanceHookEnvByObject(lu, instance, override=None):
151
  """Builds instance related env variables for hooks from an object.
152

153
  @type lu: L{LogicalUnit}
154
  @param lu: the logical unit on whose behalf we execute
155
  @type instance: L{objects.Instance}
156
  @param instance: the instance for which we should build the
157
      environment
158
  @type override: dict
159
  @param override: dictionary with key/values that will override
160
      our values
161
  @rtype: dict
162
  @return: the hook environment dictionary
163

164
  """
165
  cluster = lu.cfg.GetClusterInfo()
166
  bep = cluster.FillBE(instance)
167
  hvp = cluster.FillHV(instance)
168
  secondary_nodes = lu.cfg.GetInstanceSecondaryNodes(instance)
169
  args = {
170
    "name": instance.name,
171
    "primary_node_name": lu.cfg.GetNodeName(instance.primary_node),
172
    "secondary_node_names": lu.cfg.GetNodeNames(secondary_nodes),
173
    "os_type": instance.os,
174
    "status": instance.admin_state,
175
    "maxmem": bep[constants.BE_MAXMEM],
176
    "minmem": bep[constants.BE_MINMEM],
177
    "vcpus": bep[constants.BE_VCPUS],
178
    "nics": NICListToTuple(lu, instance.nics),
179
    "disk_template": instance.disk_template,
180
    "disks": [(disk.name, disk.uuid, disk.size, disk.mode)
181
              for disk in instance.disks],
182
    "bep": bep,
183
    "hvp": hvp,
184
    "hypervisor_name": instance.hypervisor,
185
    "tags": instance.tags,
186
  }
187
  if override:
188
    args.update(override)
189
  return BuildInstanceHookEnv(**args) # pylint: disable=W0142
190

    
191

    
192
def GetClusterDomainSecret():
193
  """Reads the cluster domain secret.
194

195
  """
196
  return utils.ReadOneLineFile(pathutils.CLUSTER_DOMAIN_SECRET_FILE,
197
                               strict=True)
198

    
199

    
200
def CheckNodeNotDrained(lu, node_uuid):
201
  """Ensure that a given node is not drained.
202

203
  @param lu: the LU on behalf of which we make the check
204
  @param node_uuid: the node to check
205
  @raise errors.OpPrereqError: if the node is drained
206

207
  """
208
  node = lu.cfg.GetNodeInfo(node_uuid)
209
  if node.drained:
210
    raise errors.OpPrereqError("Can't use drained node %s" % node.name,
211
                               errors.ECODE_STATE)
212

    
213

    
214
def CheckNodeVmCapable(lu, node_uuid):
215
  """Ensure that a given node is vm capable.
216

217
  @param lu: the LU on behalf of which we make the check
218
  @param node_uuid: the node to check
219
  @raise errors.OpPrereqError: if the node is not vm capable
220

221
  """
222
  if not lu.cfg.GetNodeInfo(node_uuid).vm_capable:
223
    raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node_uuid,
224
                               errors.ECODE_STATE)
225

    
226

    
227
def RemoveInstance(lu, feedback_fn, instance, ignore_failures):
228
  """Utility function to remove an instance.
229

230
  """
231
  logging.info("Removing block devices for instance %s", instance.name)
232

    
233
  if not RemoveDisks(lu, instance, ignore_failures=ignore_failures):
234
    if not ignore_failures:
235
      raise errors.OpExecError("Can't remove instance's disks")
236
    feedback_fn("Warning: can't remove instance's disks")
237

    
238
  logging.info("Removing instance %s out of cluster config", instance.name)
239

    
240
  lu.cfg.RemoveInstance(instance.uuid)
241

    
242
  assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
243
    "Instance lock removal conflict"
244

    
245
  # Remove lock for the instance
246
  lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
247

    
248

    
249
def RemoveDisks(lu, instance, target_node_uuid=None, ignore_failures=False):
250
  """Remove all disks for an instance.
251

252
  This abstracts away some work from `AddInstance()` and
253
  `RemoveInstance()`. Note that in case some of the devices couldn't
254
  be removed, the removal will continue with the other ones.
255

256
  @type lu: L{LogicalUnit}
257
  @param lu: the logical unit on whose behalf we execute
258
  @type instance: L{objects.Instance}
259
  @param instance: the instance whose disks we should remove
260
  @type target_node_uuid: string
261
  @param target_node_uuid: used to override the node on which to remove the
262
          disks
263
  @rtype: boolean
264
  @return: the success of the removal
265

266
  """
267
  logging.info("Removing block devices for instance %s", instance.name)
268

    
269
  all_result = True
270
  ports_to_release = set()
271
  anno_disks = AnnotateDiskParams(instance, instance.disks, lu.cfg)
272
  for (idx, device) in enumerate(anno_disks):
273
    if target_node_uuid:
274
      edata = [(target_node_uuid, device)]
275
    else:
276
      edata = device.ComputeNodeTree(instance.primary_node)
277
    for node_uuid, disk in edata:
278
      result = lu.rpc.call_blockdev_remove(node_uuid, (disk, instance))
279
      if result.fail_msg:
280
        lu.LogWarning("Could not remove disk %s on node %s,"
281
                      " continuing anyway: %s", idx,
282
                      lu.cfg.GetNodeName(node_uuid), result.fail_msg)
283
        if not (result.offline and node_uuid != instance.primary_node):
284
          all_result = False
285

    
286
    # if this is a DRBD disk, return its port to the pool
287
    if device.dev_type in constants.DTS_DRBD:
288
      ports_to_release.add(device.logical_id[2])
289

    
290
  if all_result or ignore_failures:
291
    for port in ports_to_release:
292
      lu.cfg.AddTcpUdpPort(port)
293

    
294
  CheckDiskTemplateEnabled(lu.cfg.GetClusterInfo(), instance.disk_template)
295

    
296
  if instance.disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
297
    if len(instance.disks) > 0:
298
      file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
299
    else:
300
      if instance.disk_template == constants.DT_SHARED_FILE:
301
        file_storage_dir = utils.PathJoin(lu.cfg.GetSharedFileStorageDir(),
302
                                          instance.name)
303
      else:
304
        file_storage_dir = utils.PathJoin(lu.cfg.GetFileStorageDir(),
305
                                          instance.name)
306
    if target_node_uuid:
307
      tgt = target_node_uuid
308
    else:
309
      tgt = instance.primary_node
310
    result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
311
    if result.fail_msg:
312
      lu.LogWarning("Could not remove directory '%s' on node %s: %s",
313
                    file_storage_dir, lu.cfg.GetNodeName(tgt), result.fail_msg)
314
      all_result = False
315

    
316
  return all_result
317

    
318

    
319
def NICToTuple(lu, nic):
320
  """Build a tupple of nic information.
321

322
  @type lu:  L{LogicalUnit}
323
  @param lu: the logical unit on whose behalf we execute
324
  @type nic: L{objects.NIC}
325
  @param nic: nic to convert to hooks tuple
326

327
  """
328
  cluster = lu.cfg.GetClusterInfo()
329
  filled_params = cluster.SimpleFillNIC(nic.nicparams)
330
  mode = filled_params[constants.NIC_MODE]
331
  link = filled_params[constants.NIC_LINK]
332
  vlan = filled_params[constants.NIC_VLAN]
333
  netinfo = None
334
  if nic.network:
335
    nobj = lu.cfg.GetNetwork(nic.network)
336
    netinfo = objects.Network.ToDict(nobj)
337
  return (nic.name, nic.uuid, nic.ip, nic.mac, mode, link, vlan,
338
          nic.network, netinfo)
339

    
340

    
341
def NICListToTuple(lu, nics):
342
  """Build a list of nic information tuples.
343

344
  This list is suitable to be passed to _BuildInstanceHookEnv or as a return
345
  value in LUInstanceQueryData.
346

347
  @type lu:  L{LogicalUnit}
348
  @param lu: the logical unit on whose behalf we execute
349
  @type nics: list of L{objects.NIC}
350
  @param nics: list of nics to convert to hooks tuples
351

352
  """
353
  hooks_nics = []
354
  for nic in nics:
355
    hooks_nics.append(NICToTuple(lu, nic))
356
  return hooks_nics
357

    
358

    
359
def CopyLockList(names):
360
  """Makes a copy of a list of lock names.
361

362
  Handles L{locking.ALL_SET} correctly.
363

364
  """
365
  if names == locking.ALL_SET:
366
    return locking.ALL_SET
367
  else:
368
    return names[:]
369

    
370

    
371
def ReleaseLocks(lu, level, names=None, keep=None):
372
  """Releases locks owned by an LU.
373

374
  @type lu: L{LogicalUnit}
375
  @param level: Lock level
376
  @type names: list or None
377
  @param names: Names of locks to release
378
  @type keep: list or None
379
  @param keep: Names of locks to retain
380

381
  """
382
  assert not (keep is not None and names is not None), \
383
         "Only one of the 'names' and the 'keep' parameters can be given"
384

    
385
  if names is not None:
386
    should_release = names.__contains__
387
  elif keep:
388
    should_release = lambda name: name not in keep
389
  else:
390
    should_release = None
391

    
392
  owned = lu.owned_locks(level)
393
  if not owned:
394
    # Not owning any lock at this level, do nothing
395
    pass
396

    
397
  elif should_release:
398
    retain = []
399
    release = []
400

    
401
    # Determine which locks to release
402
    for name in owned:
403
      if should_release(name):
404
        release.append(name)
405
      else:
406
        retain.append(name)
407

    
408
    assert len(lu.owned_locks(level)) == (len(retain) + len(release))
409

    
410
    # Release just some locks
411
    lu.glm.release(level, names=release)
412

    
413
    assert frozenset(lu.owned_locks(level)) == frozenset(retain)
414
  else:
415
    # Release everything
416
    lu.glm.release(level)
417

    
418
    assert not lu.glm.is_owned(level), "No locks should be owned"
419

    
420

    
421
def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
422
                                 target_group, cfg,
423
                                 _compute_fn=ComputeIPolicyInstanceViolation):
424
  """Compute if instance meets the specs of the new target group.
425

426
  @param ipolicy: The ipolicy to verify
427
  @param instance: The instance object to verify
428
  @param current_group: The current group of the instance
429
  @param target_group: The new group of the instance
430
  @type cfg: L{config.ConfigWriter}
431
  @param cfg: Cluster configuration
432
  @param _compute_fn: The function to verify ipolicy (unittest only)
433
  @see: L{ganeti.cmdlib.common.ComputeIPolicySpecViolation}
434

435
  """
436
  if current_group == target_group:
437
    return []
438
  else:
439
    return _compute_fn(ipolicy, instance, cfg)
440

    
441

    
442
def CheckTargetNodeIPolicy(lu, ipolicy, instance, node, cfg, ignore=False,
443
                           _compute_fn=_ComputeIPolicyNodeViolation):
444
  """Checks that the target node is correct in terms of instance policy.
445

446
  @param ipolicy: The ipolicy to verify
447
  @param instance: The instance object to verify
448
  @param node: The new node to relocate
449
  @type cfg: L{config.ConfigWriter}
450
  @param cfg: Cluster configuration
451
  @param ignore: Ignore violations of the ipolicy
452
  @param _compute_fn: The function to verify ipolicy (unittest only)
453
  @see: L{ganeti.cmdlib.common.ComputeIPolicySpecViolation}
454

455
  """
456
  primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
457
  res = _compute_fn(ipolicy, instance, primary_node.group, node.group, cfg)
458

    
459
  if res:
460
    msg = ("Instance does not meet target node group's (%s) instance"
461
           " policy: %s") % (node.group, utils.CommaJoin(res))
462
    if ignore:
463
      lu.LogWarning(msg)
464
    else:
465
      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
466

    
467

    
468
def GetInstanceInfoText(instance):
469
  """Compute that text that should be added to the disk's metadata.
470

471
  """
472
  return "originstname+%s" % instance.name
473

    
474

    
475
def CheckNodeFreeMemory(lu, node_uuid, reason, requested, hvname, hvparams):
476
  """Checks if a node has enough free memory.
477

478
  This function checks if a given node has the needed amount of free
479
  memory. In case the node has less memory or we cannot get the
480
  information from the node, this function raises an OpPrereqError
481
  exception.
482

483
  @type lu: C{LogicalUnit}
484
  @param lu: a logical unit from which we get configuration data
485
  @type node_uuid: C{str}
486
  @param node_uuid: the node to check
487
  @type reason: C{str}
488
  @param reason: string to use in the error message
489
  @type requested: C{int}
490
  @param requested: the amount of memory in MiB to check for
491
  @type hvname: string
492
  @param hvname: the hypervisor's name
493
  @type hvparams: dict of strings
494
  @param hvparams: the hypervisor's parameters
495
  @rtype: integer
496
  @return: node current free memory
497
  @raise errors.OpPrereqError: if the node doesn't have enough memory, or
498
      we cannot check the node
499

500
  """
501
  node_name = lu.cfg.GetNodeName(node_uuid)
502
  nodeinfo = lu.rpc.call_node_info([node_uuid], None, [(hvname, hvparams)])
503
  nodeinfo[node_uuid].Raise("Can't get data from node %s" % node_name,
504
                            prereq=True, ecode=errors.ECODE_ENVIRON)
505
  (_, _, (hv_info, )) = nodeinfo[node_uuid].payload
506

    
507
  free_mem = hv_info.get("memory_free", None)
508
  if not isinstance(free_mem, int):
509
    raise errors.OpPrereqError("Can't compute free memory on node %s, result"
510
                               " was '%s'" % (node_name, free_mem),
511
                               errors.ECODE_ENVIRON)
512
  if requested > free_mem:
513
    raise errors.OpPrereqError("Not enough memory on node %s for %s:"
514
                               " needed %s MiB, available %s MiB" %
515
                               (node_name, reason, requested, free_mem),
516
                               errors.ECODE_NORES)
517
  return free_mem
518

    
519

    
520
def CheckInstanceBridgesExist(lu, instance, node_uuid=None):
521
  """Check that the brigdes needed by an instance exist.
522

523
  """
524
  if node_uuid is None:
525
    node_uuid = instance.primary_node
526
  CheckNicsBridgesExist(lu, instance.nics, node_uuid)
527

    
528

    
529
def CheckNicsBridgesExist(lu, nics, node_uuid):
530
  """Check that the brigdes needed by a list of nics exist.
531

532
  """
533
  cluster = lu.cfg.GetClusterInfo()
534
  paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in nics]
535
  brlist = [params[constants.NIC_LINK] for params in paramslist
536
            if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
537
  if brlist:
538
    result = lu.rpc.call_bridges_exist(node_uuid, brlist)
539
    result.Raise("Error checking bridges on destination node '%s'" %
540
                 lu.cfg.GetNodeName(node_uuid), prereq=True,
541
                 ecode=errors.ECODE_ENVIRON)
542

    
543

    
544
def CheckNodeHasOS(lu, node_uuid, os_name, force_variant):
545
  """Ensure that a node supports a given OS.
546

547
  @param lu: the LU on behalf of which we make the check
548
  @param node_uuid: the node to check
549
  @param os_name: the OS to query about
550
  @param force_variant: whether to ignore variant errors
551
  @raise errors.OpPrereqError: if the node is not supporting the OS
552

553
  """
554
  result = lu.rpc.call_os_get(node_uuid, os_name)
555
  result.Raise("OS '%s' not in supported OS list for node %s" %
556
               (os_name, lu.cfg.GetNodeName(node_uuid)),
557
               prereq=True, ecode=errors.ECODE_INVAL)
558
  if not force_variant:
559
    _CheckOSVariant(result.payload, os_name)
560

    
561

    
562
def _CheckOSVariant(os_obj, name):
563
  """Check whether an OS name conforms to the os variants specification.
564

565
  @type os_obj: L{objects.OS}
566
  @param os_obj: OS object to check
567
  @type name: string
568
  @param name: OS name passed by the user, to check for validity
569

570
  """
571
  variant = objects.OS.GetVariant(name)
572
  if not os_obj.supported_variants:
573
    if variant:
574
      raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
575
                                 " passed)" % (os_obj.name, variant),
576
                                 errors.ECODE_INVAL)
577
    return
578
  if not variant:
579
    raise errors.OpPrereqError("OS name must include a variant",
580
                               errors.ECODE_INVAL)
581

    
582
  if variant not in os_obj.supported_variants:
583
    raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)