Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_utils.py @ 87ed6b79

History | View | Annotate | Download (19.3 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
  logging.debug("Lu %s ReleaseLocks %s names=%s, keep=%s",
382
                lu.wconfdcontext, level, names, keep)
383
  assert not (keep is not None and names is not None), \
384
         "Only one of the 'names' and the 'keep' parameters can be given"
385

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

    
393
  levelname = locking.LEVEL_NAMES[level]
394

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

    
400
  elif should_release:
401
    retain = []
402
    release = []
403

    
404
    # Determine which locks to release
405
    for name in owned:
406
      if should_release(name):
407
        release.append(name)
408
      else:
409
        retain.append(name)
410

    
411
    assert len(lu.owned_locks(level)) == (len(retain) + len(release))
412

    
413
    # Release just some locks
414
    lu.WConfdClient().TryUpdateLocks(
415
      lu.release_request(level, release))
416
    assert frozenset(lu.owned_locks(level)) == frozenset(retain)
417
  else:
418
    lu.WConfdClient().FreeLocksLevel(levelname)
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)