Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_utils.py @ 0c3d9c7c

History | View | Annotate | Download (18.8 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
      lu.cfg.SetDiskID(disk, node_uuid)
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.LDS_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.DTS_FILEBASED:
297
    file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
298
    if target_node_uuid:
299
      tgt = target_node_uuid
300
    else:
301
      tgt = instance.primary_node
302
    result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
303
    if result.fail_msg:
304
      lu.LogWarning("Could not remove directory '%s' on node %s: %s",
305
                    file_storage_dir, lu.cfg.GetNodeName(tgt), result.fail_msg)
306
      all_result = False
307

    
308
  return all_result
309

    
310

    
311
def NICToTuple(lu, nic):
312
  """Build a tupple of nic information.
313

314
  @type lu:  L{LogicalUnit}
315
  @param lu: the logical unit on whose behalf we execute
316
  @type nic: L{objects.NIC}
317
  @param nic: nic to convert to hooks tuple
318

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

    
332

    
333
def NICListToTuple(lu, nics):
334
  """Build a list of nic information tuples.
335

336
  This list is suitable to be passed to _BuildInstanceHookEnv or as a return
337
  value in LUInstanceQueryData.
338

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

344
  """
345
  hooks_nics = []
346
  for nic in nics:
347
    hooks_nics.append(NICToTuple(lu, nic))
348
  return hooks_nics
349

    
350

    
351
def CopyLockList(names):
352
  """Makes a copy of a list of lock names.
353

354
  Handles L{locking.ALL_SET} correctly.
355

356
  """
357
  if names == locking.ALL_SET:
358
    return locking.ALL_SET
359
  else:
360
    return names[:]
361

    
362

    
363
def ReleaseLocks(lu, level, names=None, keep=None):
364
  """Releases locks owned by an LU.
365

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

373
  """
374
  assert not (keep is not None and names is not None), \
375
         "Only one of the 'names' and the 'keep' parameters can be given"
376

    
377
  if names is not None:
378
    should_release = names.__contains__
379
  elif keep:
380
    should_release = lambda name: name not in keep
381
  else:
382
    should_release = None
383

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

    
389
  elif should_release:
390
    retain = []
391
    release = []
392

    
393
    # Determine which locks to release
394
    for name in owned:
395
      if should_release(name):
396
        release.append(name)
397
      else:
398
        retain.append(name)
399

    
400
    assert len(lu.owned_locks(level)) == (len(retain) + len(release))
401

    
402
    # Release just some locks
403
    lu.glm.release(level, names=release)
404

    
405
    assert frozenset(lu.owned_locks(level)) == frozenset(retain)
406
  else:
407
    # Release everything
408
    lu.glm.release(level)
409

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

    
412

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

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

427
  """
428
  if current_group == target_group:
429
    return []
430
  else:
431
    return _compute_fn(ipolicy, instance, cfg)
432

    
433

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

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

447
  """
448
  primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
449
  res = _compute_fn(ipolicy, instance, primary_node.group, node.group, cfg)
450

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

    
459

    
460
def GetInstanceInfoText(instance):
461
  """Compute that text that should be added to the disk's metadata.
462

463
  """
464
  return "originstname+%s" % instance.name
465

    
466

    
467
def CheckNodeFreeMemory(lu, node_uuid, reason, requested, hvname, hvparams):
468
  """Checks if a node has enough free memory.
469

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

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

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

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

    
511

    
512
def CheckInstanceBridgesExist(lu, instance, node_uuid=None):
513
  """Check that the brigdes needed by an instance exist.
514

515
  """
516
  if node_uuid is None:
517
    node_uuid = instance.primary_node
518
  CheckNicsBridgesExist(lu, instance.nics, node_uuid)
519

    
520

    
521
def CheckNicsBridgesExist(lu, nics, node_uuid):
522
  """Check that the brigdes needed by a list of nics exist.
523

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

    
535

    
536
def CheckNodeHasOS(lu, node_uuid, os_name, force_variant):
537
  """Ensure that a node supports a given OS.
538

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

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

    
553

    
554
def _CheckOSVariant(os_obj, name):
555
  """Check whether an OS name conforms to the os variants specification.
556

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

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

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