Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (18.1 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, secondary_nodes, os_type, status,
39
                         minmem, maxmem, vcpus, nics, disk_template, disks,
40
                         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: string
48
  @param primary_node: the name of the instance's primary node
49
  @type secondary_nodes: list
50
  @param secondary_nodes: 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,
84
    "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
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": instance.primary_node,
168
    "secondary_nodes": 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):
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: the node to check
201
  @raise errors.OpPrereqError: if the node is drained
202

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

    
208

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

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

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

    
221

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

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

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

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

    
235
  lu.cfg.RemoveInstance(instance.name)
236

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

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

    
243

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

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

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

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

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

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

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

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

    
300
  return all_result
301

    
302

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

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

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

    
322

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

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

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

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

    
340

    
341
def CopyLockList(names):
342
  """Makes a copy of a list of lock names.
343

344
  Handles L{locking.ALL_SET} correctly.
345

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

    
352

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

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

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

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

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

    
379
  elif should_release:
380
    retain = []
381
    release = []
382

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

    
390
    assert len(lu.owned_locks(level)) == (len(retain) + len(release))
391

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

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

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

    
402

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

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

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

    
423

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

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

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

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

    
449

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

453
  """
454
  return "originstname+%s" % instance.name
455

    
456

    
457
def CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
458
  """Checks if a node has enough free memory.
459

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

465
  @type lu: C{LogicalUnit}
466
  @param lu: a logical unit from which we get configuration data
467
  @type node: C{str}
468
  @param node: the node to check
469
  @type reason: C{str}
470
  @param reason: string to use in the error message
471
  @type requested: C{int}
472
  @param requested: the amount of memory in MiB to check for
473
  @type hypervisor_name: C{str}
474
  @param hypervisor_name: the hypervisor to ask for memory stats
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
  nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name], False)
482
  nodeinfo[node].Raise("Can't get data from node %s" % node,
483
                       prereq=True, ecode=errors.ECODE_ENVIRON)
484
  (_, _, (hv_info, )) = nodeinfo[node].payload
485

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

    
498

    
499
def CheckInstanceBridgesExist(lu, instance, node=None):
500
  """Check that the brigdes needed by an instance exist.
501

502
  """
503
  if node is None:
504
    node = instance.primary_node
505
  CheckNicsBridgesExist(lu, instance.nics, node)
506

    
507

    
508
def CheckNicsBridgesExist(lu, target_nics, target_node):
509
  """Check that the brigdes needed by a list of nics exist.
510

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

    
521

    
522
def CheckNodeHasOS(lu, node, os_name, force_variant):
523
  """Ensure that a node supports a given OS.
524

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

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

    
539

    
540
def _CheckOSVariant(os_obj, name):
541
  """Check whether an OS name conforms to the os variants specification.
542

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

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

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