Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_utils.py @ 1c4910f7

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

    
190

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

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

    
198

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

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

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

    
212

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

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

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

    
225

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

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

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

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

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

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

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

    
247

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

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

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

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

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

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

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

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

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

    
315
  return all_result
316

    
317

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

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

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

    
339

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

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

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

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

    
357

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

361
  Handles L{locking.ALL_SET} correctly.
362

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

    
369

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

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

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

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

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

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

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

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

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

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

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

    
419

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

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

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

    
440

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

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

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

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

    
466

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

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

    
473

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

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

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

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

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

    
518

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

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

    
527

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

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

    
542

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

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

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

    
560

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

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

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

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