Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_utils.py @ 763ad5be

History | View | Annotate | Download (14.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, _, 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": instance.primary_node,
164
    "secondary_nodes": 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):
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: the node to check
197
  @raise errors.OpPrereqError: if the node is drained
198

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

    
204

    
205
def _CheckNodeVmCapable(lu, node):
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: the node to check
210
  @raise errors.OpPrereqError: if the node is not vm capable
211

212
  """
213
  if not lu.cfg.GetNodeInfo(node).vm_capable:
214
    raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
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=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: string
252
  @param target_node: used to override the node on which to remove the disks
253
  @rtype: boolean
254
  @return: the success of the removal
255

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

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

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

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

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

    
296
  return all_result
297

    
298

    
299
def _NICToTuple(lu, nic):
300
  """Build a tupple of nic information.
301

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

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

    
318

    
319
def _NICListToTuple(lu, nics):
320
  """Build a list of nic information tuples.
321

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

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

330
  """
331
  hooks_nics = []
332
  for nic in nics:
333
    hooks_nics.append(_NICToTuple(lu, nic))
334
  return hooks_nics
335

    
336

    
337
def _CopyLockList(names):
338
  """Makes a copy of a list of lock names.
339

340
  Handles L{locking.ALL_SET} correctly.
341

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

    
348

    
349
def _ReleaseLocks(lu, level, names=None, keep=None):
350
  """Releases locks owned by an LU.
351

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

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

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

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

    
375
  elif should_release:
376
    retain = []
377
    release = []
378

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

    
386
    assert len(lu.owned_locks(level)) == (len(retain) + len(release))
387

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

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

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

    
398

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

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

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

    
419

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

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

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

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

    
445

    
446
def _GetInstanceInfoText(instance):
447
  """Compute that text that should be added to the disk's metadata.
448

449
  """
450
  return "originstname+%s" % instance.name