Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_utils.py @ 7e219d1b

History | View | Annotate | Download (19 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, net, netinfo)
63
      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, net, netinfo) in enumerate(nics):
98
      if ip is None:
99
        ip = ""
100
      if name:
101
        env["INSTANCE_NIC%d_NAME" % idx] = name
102
      env["INSTANCE_NIC%d_UUID" % idx] = uuid
103
      env["INSTANCE_NIC%d_IP" % idx] = ip
104
      env["INSTANCE_NIC%d_MAC" % idx] = mac
105
      env["INSTANCE_NIC%d_MODE" % idx] = mode
106
      env["INSTANCE_NIC%d_LINK" % idx] = link
107
      if netinfo:
108
        nobj = objects.Network.FromDict(netinfo)
109
        env.update(nobj.HooksDict("INSTANCE_NIC%d_" % idx))
110
      elif network:
111
        # FIXME: broken network reference: the instance NIC specifies a
112
        # network, but the relevant network entry was not in the config. This
113
        # should be made impossible.
114
        env["INSTANCE_NIC%d_NETWORK_NAME" % idx] = net
115
      if mode == constants.NIC_MODE_BRIDGED:
116
        env["INSTANCE_NIC%d_BRIDGE" % idx] = link
117
  else:
118
    nic_count = 0
119

    
120
  env["INSTANCE_NIC_COUNT"] = nic_count
121

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

    
133
  env["INSTANCE_DISK_COUNT"] = disk_count
134

    
135
  if not tags:
136
    tags = []
137

    
138
  env["INSTANCE_TAGS"] = " ".join(tags)
139

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

    
144
  return env
145

    
146

    
147
def BuildInstanceHookEnvByObject(lu, instance, override=None):
148
  """Builds instance related env variables for hooks from an object.
149

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

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

    
187

    
188
def GetClusterDomainSecret():
189
  """Reads the cluster domain secret.
190

191
  """
192
  return utils.ReadOneLineFile(pathutils.CLUSTER_DOMAIN_SECRET_FILE,
193
                               strict=True)
194

    
195

    
196
def CheckNodeNotDrained(lu, node_uuid):
197
  """Ensure that a given node is not drained.
198

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

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

    
209

    
210
def CheckNodeVmCapable(lu, node_uuid):
211
  """Ensure that a given node is vm capable.
212

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

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

    
222

    
223
def RemoveInstance(lu, feedback_fn, instance, ignore_failures):
224
  """Utility function to remove an instance.
225

226
  """
227
  logging.info("Removing block devices for instance %s", instance.name)
228

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

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

    
236
  lu.cfg.RemoveInstance(instance.uuid)
237

    
238
  assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
239
    "Instance lock removal conflict"
240

    
241
  # Remove lock for the instance
242
  lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
243

    
244

    
245
def RemoveDisks(lu, instance, target_node_uuid=None, ignore_failures=False):
246
  """Remove all disks for an instance.
247

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

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

262
  """
263
  logging.info("Removing block devices for instance %s", instance.name)
264

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

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

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

    
291
  CheckDiskTemplateEnabled(lu.cfg.GetClusterInfo(), instance.disk_template)
292

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

    
313
  return all_result
314

    
315

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

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

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

    
335

    
336
def NICListToTuple(lu, nics):
337
  """Build a list of nic information tuples.
338

339
  This list is suitable to be passed to _BuildInstanceHookEnv or as a return
340
  value in LUInstanceQueryData.
341

342
  @type lu:  L{LogicalUnit}
343
  @param lu: the logical unit on whose behalf we execute
344
  @type nics: list of L{objects.NIC}
345
  @param nics: list of nics to convert to hooks tuples
346

347
  """
348
  hooks_nics = []
349
  for nic in nics:
350
    hooks_nics.append(NICToTuple(lu, nic))
351
  return hooks_nics
352

    
353

    
354
def CopyLockList(names):
355
  """Makes a copy of a list of lock names.
356

357
  Handles L{locking.ALL_SET} correctly.
358

359
  """
360
  if names == locking.ALL_SET:
361
    return locking.ALL_SET
362
  else:
363
    return names[:]
364

    
365

    
366
def ReleaseLocks(lu, level, names=None, keep=None):
367
  """Releases locks owned by an LU.
368

369
  @type lu: L{LogicalUnit}
370
  @param level: Lock level
371
  @type names: list or None
372
  @param names: Names of locks to release
373
  @type keep: list or None
374
  @param keep: Names of locks to retain
375

376
  """
377
  assert not (keep is not None and names is not None), \
378
         "Only one of the 'names' and the 'keep' parameters can be given"
379

    
380
  if names is not None:
381
    should_release = names.__contains__
382
  elif keep:
383
    should_release = lambda name: name not in keep
384
  else:
385
    should_release = None
386

    
387
  owned = lu.owned_locks(level)
388
  if not owned:
389
    # Not owning any lock at this level, do nothing
390
    pass
391

    
392
  elif should_release:
393
    retain = []
394
    release = []
395

    
396
    # Determine which locks to release
397
    for name in owned:
398
      if should_release(name):
399
        release.append(name)
400
      else:
401
        retain.append(name)
402

    
403
    assert len(lu.owned_locks(level)) == (len(retain) + len(release))
404

    
405
    # Release just some locks
406
    lu.glm.release(level, names=release)
407

    
408
    assert frozenset(lu.owned_locks(level)) == frozenset(retain)
409
  else:
410
    # Release everything
411
    lu.glm.release(level)
412

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

    
415

    
416
def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
417
                                 target_group, cfg,
418
                                 _compute_fn=ComputeIPolicyInstanceViolation):
419
  """Compute if instance meets the specs of the new target group.
420

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

430
  """
431
  if current_group == target_group:
432
    return []
433
  else:
434
    return _compute_fn(ipolicy, instance, cfg)
435

    
436

    
437
def CheckTargetNodeIPolicy(lu, ipolicy, instance, node, cfg, ignore=False,
438
                           _compute_fn=_ComputeIPolicyNodeViolation):
439
  """Checks that the target node is correct in terms of instance policy.
440

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

450
  """
451
  primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
452
  res = _compute_fn(ipolicy, instance, primary_node.group, node.group, cfg)
453

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

    
462

    
463
def GetInstanceInfoText(instance):
464
  """Compute that text that should be added to the disk's metadata.
465

466
  """
467
  return "originstname+%s" % instance.name
468

    
469

    
470
def CheckNodeFreeMemory(lu, node_uuid, reason, requested, hvname, hvparams):
471
  """Checks if a node has enough free memory.
472

473
  This function checks if a given node has the needed amount of free
474
  memory. In case the node has less memory or we cannot get the
475
  information from the node, this function raises an OpPrereqError
476
  exception.
477

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

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

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

    
514

    
515
def CheckInstanceBridgesExist(lu, instance, node_uuid=None):
516
  """Check that the brigdes needed by an instance exist.
517

518
  """
519
  if node_uuid is None:
520
    node_uuid = instance.primary_node
521
  CheckNicsBridgesExist(lu, instance.nics, node_uuid)
522

    
523

    
524
def CheckNicsBridgesExist(lu, nics, node_uuid):
525
  """Check that the brigdes needed by a list of nics exist.
526

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

    
538

    
539
def CheckNodeHasOS(lu, node_uuid, os_name, force_variant):
540
  """Ensure that a node supports a given OS.
541

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

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

    
556

    
557
def _CheckOSVariant(os_obj, name):
558
  """Check whether an OS name conforms to the os variants specification.
559

560
  @type os_obj: L{objects.OS}
561
  @param os_obj: OS object to check
562
  @type name: string
563
  @param name: OS name passed by the user, to check for validity
564

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

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