Statistics
| Branch: | Tag: | Revision:

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

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

    
118
  env["INSTANCE_NIC_COUNT"] = nic_count
119

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

    
129
  env["INSTANCE_DISK_COUNT"] = disk_count
130

    
131
  if not tags:
132
    tags = []
133

    
134
  env["INSTANCE_TAGS"] = " ".join(tags)
135

    
136
  for source, kind in [(bep, "BE"), (hvp, "HV")]:
137
    for key, value in source.items():
138
      env["INSTANCE_%s_%s" % (kind, key)] = value
139

    
140
  return env
141

    
142

    
143
def BuildInstanceHookEnvByObject(lu, instance, override=None):
144
  """Builds instance related env variables for hooks from an object.
145

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

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

    
183

    
184
def GetClusterDomainSecret():
185
  """Reads the cluster domain secret.
186

187
  """
188
  return utils.ReadOneLineFile(pathutils.CLUSTER_DOMAIN_SECRET_FILE,
189
                               strict=True)
190

    
191

    
192
def CheckNodeNotDrained(lu, node_uuid):
193
  """Ensure that a given node is not drained.
194

195
  @param lu: the LU on behalf of which we make the check
196
  @param node_uuid: the node to check
197
  @raise errors.OpPrereqError: if the node is drained
198

199
  """
200
  if lu.cfg.GetNodeInfo(node_uuid).drained:
201
    raise errors.OpPrereqError("Can't use drained node %s" % node_uuid,
202
                               errors.ECODE_STATE)
203

    
204

    
205
def CheckNodeVmCapable(lu, node_uuid):
206
  """Ensure that a given node is vm capable.
207

208
  @param lu: the LU on behalf of which we make the check
209
  @param node_uuid: the node to check
210
  @raise errors.OpPrereqError: if the node is not vm capable
211

212
  """
213
  if not lu.cfg.GetNodeInfo(node_uuid).vm_capable:
214
    raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node_uuid,
215
                               errors.ECODE_STATE)
216

    
217

    
218
def RemoveInstance(lu, feedback_fn, instance, ignore_failures):
219
  """Utility function to remove an instance.
220

221
  """
222
  logging.info("Removing block devices for instance %s", instance.name)
223

    
224
  if not RemoveDisks(lu, instance, ignore_failures=ignore_failures):
225
    if not ignore_failures:
226
      raise errors.OpExecError("Can't remove instance's disks")
227
    feedback_fn("Warning: can't remove instance's disks")
228

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

    
231
  lu.cfg.RemoveInstance(instance.name)
232

    
233
  assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
234
    "Instance lock removal conflict"
235

    
236
  # Remove lock for the instance
237
  lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
238

    
239

    
240
def RemoveDisks(lu, instance, target_node_uuid=None, ignore_failures=False):
241
  """Remove all disks for an instance.
242

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

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

257
  """
258
  logging.info("Removing block devices for instance %s", instance.name)
259

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

    
278
    # if this is a DRBD disk, return its port to the pool
279
    if device.dev_type in constants.LDS_DRBD:
280
      ports_to_release.add(device.logical_id[2])
281

    
282
  if all_result or ignore_failures:
283
    for port in ports_to_release:
284
      lu.cfg.AddTcpUdpPort(port)
285

    
286
  if instance.disk_template in constants.DTS_FILEBASED:
287
    file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
288
    if target_node_uuid:
289
      tgt = target_node_uuid
290
    else:
291
      tgt = instance.primary_node
292
    result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
293
    if result.fail_msg:
294
      lu.LogWarning("Could not remove directory '%s' on node %s: %s",
295
                    file_storage_dir, lu.cfg.GetNodeName(tgt), result.fail_msg)
296
      all_result = False
297

    
298
  return all_result
299

    
300

    
301
def NICToTuple(lu, nic):
302
  """Build a tupple of nic information.
303

304
  @type lu:  L{LogicalUnit}
305
  @param lu: the logical unit on whose behalf we execute
306
  @type nic: L{objects.NIC}
307
  @param nic: nic to convert to hooks tuple
308

309
  """
310
  cluster = lu.cfg.GetClusterInfo()
311
  filled_params = cluster.SimpleFillNIC(nic.nicparams)
312
  mode = filled_params[constants.NIC_MODE]
313
  link = filled_params[constants.NIC_LINK]
314
  netinfo = None
315
  if nic.network:
316
    nobj = lu.cfg.GetNetwork(nic.network)
317
    netinfo = objects.Network.ToDict(nobj)
318
  return (nic.name, nic.uuid, nic.ip, nic.mac, mode, link, nic.network, netinfo)
319

    
320

    
321
def NICListToTuple(lu, nics):
322
  """Build a list of nic information tuples.
323

324
  This list is suitable to be passed to _BuildInstanceHookEnv or as a return
325
  value in LUInstanceQueryData.
326

327
  @type lu:  L{LogicalUnit}
328
  @param lu: the logical unit on whose behalf we execute
329
  @type nics: list of L{objects.NIC}
330
  @param nics: list of nics to convert to hooks tuples
331

332
  """
333
  hooks_nics = []
334
  for nic in nics:
335
    hooks_nics.append(NICToTuple(lu, nic))
336
  return hooks_nics
337

    
338

    
339
def CopyLockList(names):
340
  """Makes a copy of a list of lock names.
341

342
  Handles L{locking.ALL_SET} correctly.
343

344
  """
345
  if names == locking.ALL_SET:
346
    return locking.ALL_SET
347
  else:
348
    return names[:]
349

    
350

    
351
def ReleaseLocks(lu, level, names=None, keep=None):
352
  """Releases locks owned by an LU.
353

354
  @type lu: L{LogicalUnit}
355
  @param level: Lock level
356
  @type names: list or None
357
  @param names: Names of locks to release
358
  @type keep: list or None
359
  @param keep: Names of locks to retain
360

361
  """
362
  assert not (keep is not None and names is not None), \
363
         "Only one of the 'names' and the 'keep' parameters can be given"
364

    
365
  if names is not None:
366
    should_release = names.__contains__
367
  elif keep:
368
    should_release = lambda name: name not in keep
369
  else:
370
    should_release = None
371

    
372
  owned = lu.owned_locks(level)
373
  if not owned:
374
    # Not owning any lock at this level, do nothing
375
    pass
376

    
377
  elif should_release:
378
    retain = []
379
    release = []
380

    
381
    # Determine which locks to release
382
    for name in owned:
383
      if should_release(name):
384
        release.append(name)
385
      else:
386
        retain.append(name)
387

    
388
    assert len(lu.owned_locks(level)) == (len(retain) + len(release))
389

    
390
    # Release just some locks
391
    lu.glm.release(level, names=release)
392

    
393
    assert frozenset(lu.owned_locks(level)) == frozenset(retain)
394
  else:
395
    # Release everything
396
    lu.glm.release(level)
397

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

    
400

    
401
def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
402
                                 target_group, cfg,
403
                                 _compute_fn=ComputeIPolicyInstanceViolation):
404
  """Compute if instance meets the specs of the new target group.
405

406
  @param ipolicy: The ipolicy to verify
407
  @param instance: The instance object to verify
408
  @param current_group: The current group of the instance
409
  @param target_group: The new group of the instance
410
  @type cfg: L{config.ConfigWriter}
411
  @param cfg: Cluster configuration
412
  @param _compute_fn: The function to verify ipolicy (unittest only)
413
  @see: L{ganeti.cmdlib.common.ComputeIPolicySpecViolation}
414

415
  """
416
  if current_group == target_group:
417
    return []
418
  else:
419
    return _compute_fn(ipolicy, instance, cfg)
420

    
421

    
422
def CheckTargetNodeIPolicy(lu, ipolicy, instance, node, cfg, ignore=False,
423
                           _compute_fn=_ComputeIPolicyNodeViolation):
424
  """Checks that the target node is correct in terms of instance policy.
425

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

435
  """
436
  primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
437
  res = _compute_fn(ipolicy, instance, primary_node.group, node.group, cfg)
438

    
439
  if res:
440
    msg = ("Instance does not meet target node group's (%s) instance"
441
           " policy: %s") % (node.group, utils.CommaJoin(res))
442
    if ignore:
443
      lu.LogWarning(msg)
444
    else:
445
      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
446

    
447

    
448
def GetInstanceInfoText(instance):
449
  """Compute that text that should be added to the disk's metadata.
450

451
  """
452
  return "originstname+%s" % instance.name
453

    
454

    
455
def CheckNodeFreeMemory(lu, node_uuid, reason, requested, hvname, hvparams):
456
  """Checks if a node has enough free memory.
457

458
  This function checks if a given node has the needed amount of free
459
  memory. In case the node has less memory or we cannot get the
460
  information from the node, this function raises an OpPrereqError
461
  exception.
462

463
  @type lu: C{LogicalUnit}
464
  @param lu: a logical unit from which we get configuration data
465
  @type node_uuid: C{str}
466
  @param node_uuid: the node to check
467
  @type reason: C{str}
468
  @param reason: string to use in the error message
469
  @type requested: C{int}
470
  @param requested: the amount of memory in MiB to check for
471
  @type hvname: string
472
  @param hvname: the hypervisor's name
473
  @type hvparams: dict of strings
474
  @param hvparams: the hypervisor's parameters
475
  @rtype: integer
476
  @return: node current free memory
477
  @raise errors.OpPrereqError: if the node doesn't have enough memory, or
478
      we cannot check the node
479

480
  """
481
  node_name = lu.cfg.GetNodeName(node_uuid)
482
  nodeinfo = lu.rpc.call_node_info([node_uuid], None, [(hvname, hvparams)],
483
                                   False)
484
  nodeinfo[node_uuid].Raise("Can't get data from node %s" % node_name,
485
                            prereq=True, ecode=errors.ECODE_ENVIRON)
486
  (_, _, (hv_info, )) = nodeinfo[node_uuid].payload
487

    
488
  free_mem = hv_info.get("memory_free", None)
489
  if not isinstance(free_mem, int):
490
    raise errors.OpPrereqError("Can't compute free memory on node %s, result"
491
                               " was '%s'" % (node_name, free_mem),
492
                               errors.ECODE_ENVIRON)
493
  if requested > free_mem:
494
    raise errors.OpPrereqError("Not enough memory on node %s for %s:"
495
                               " needed %s MiB, available %s MiB" %
496
                               (node_name, reason, requested, free_mem),
497
                               errors.ECODE_NORES)
498
  return free_mem
499

    
500

    
501
def CheckInstanceBridgesExist(lu, instance, node_uuid=None):
502
  """Check that the brigdes needed by an instance exist.
503

504
  """
505
  if node_uuid is None:
506
    node_uuid = instance.primary_node
507
  CheckNicsBridgesExist(lu, instance.nics, node_uuid)
508

    
509

    
510
def CheckNicsBridgesExist(lu, nics, node_uuid):
511
  """Check that the brigdes needed by a list of nics exist.
512

513
  """
514
  cluster = lu.cfg.GetClusterInfo()
515
  paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in nics]
516
  brlist = [params[constants.NIC_LINK] for params in paramslist
517
            if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
518
  if brlist:
519
    result = lu.rpc.call_bridges_exist(node_uuid, brlist)
520
    result.Raise("Error checking bridges on destination node '%s'" %
521
                 lu.cfg.GetNodeName(node_uuid), prereq=True,
522
                 ecode=errors.ECODE_ENVIRON)
523

    
524

    
525
def CheckNodeHasOS(lu, node_uuid, os_name, force_variant):
526
  """Ensure that a node supports a given OS.
527

528
  @param lu: the LU on behalf of which we make the check
529
  @param node_uuid: the node to check
530
  @param os_name: the OS to query about
531
  @param force_variant: whether to ignore variant errors
532
  @raise errors.OpPrereqError: if the node is not supporting the OS
533

534
  """
535
  result = lu.rpc.call_os_get(node_uuid, os_name)
536
  result.Raise("OS '%s' not in supported OS list for node %s" %
537
               (os_name, lu.cfg.GetNodeName(node_uuid)),
538
               prereq=True, ecode=errors.ECODE_INVAL)
539
  if not force_variant:
540
    _CheckOSVariant(result.payload, os_name)
541

    
542

    
543
def _CheckOSVariant(os_obj, name):
544
  """Check whether an OS name conforms to the os variants specification.
545

546
  @type os_obj: L{objects.OS}
547
  @param os_obj: OS object to check
548
  @type name: string
549
  @param name: OS name passed by the user, to check for validity
550

551
  """
552
  variant = objects.OS.GetVariant(name)
553
  if not os_obj.supported_variants:
554
    if variant:
555
      raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
556
                                 " passed)" % (os_obj.name, variant),
557
                                 errors.ECODE_INVAL)
558
    return
559
  if not variant:
560
    raise errors.OpPrereqError("OS name must include a variant",
561
                               errors.ECODE_INVAL)
562

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