Merge branch 'stable-2.8' into stable-2.9
[ganeti-local] / lib / cmdlib / instance_utils.py
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, 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, 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_name": lu.cfg.GetNodeName(instance.primary_node),
168     "secondary_node_names": lu.cfg.GetNodeNames(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_uuid):
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_uuid: the node to check
201   @raise errors.OpPrereqError: if the node is drained
202
203   """
204   node = lu.cfg.GetNodeInfo(node_uuid)
205   if node.drained:
206     raise errors.OpPrereqError("Can't use drained node %s" % node.name,
207                                errors.ECODE_STATE)
208
209
210 def CheckNodeVmCapable(lu, node_uuid):
211   """Ensure that a given node is vm capable.
212
213   @param lu: the LU on behalf of which we make the check
214   @param node_uuid: the node to check
215   @raise errors.OpPrereqError: if the node is not vm capable
216
217   """
218   if not lu.cfg.GetNodeInfo(node_uuid).vm_capable:
219     raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node_uuid,
220                                errors.ECODE_STATE)
221
222
223 def RemoveInstance(lu, feedback_fn, instance, ignore_failures):
224   """Utility function to remove an instance.
225
226   """
227   logging.info("Removing block devices for instance %s", instance.name)
228
229   if not RemoveDisks(lu, instance, ignore_failures=ignore_failures):
230     if not ignore_failures:
231       raise errors.OpExecError("Can't remove instance's disks")
232     feedback_fn("Warning: can't remove instance's disks")
233
234   logging.info("Removing instance %s out of cluster config", instance.name)
235
236   lu.cfg.RemoveInstance(instance.uuid)
237
238   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
239     "Instance lock removal conflict"
240
241   # Remove lock for the instance
242   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
243
244
245 def RemoveDisks(lu, instance, target_node_uuid=None, ignore_failures=False):
246   """Remove all disks for an instance.
247
248   This abstracts away some work from `AddInstance()` and
249   `RemoveInstance()`. Note that in case some of the devices couldn't
250   be removed, the removal will continue with the other ones.
251
252   @type lu: L{LogicalUnit}
253   @param lu: the logical unit on whose behalf we execute
254   @type instance: L{objects.Instance}
255   @param instance: the instance whose disks we should remove
256   @type target_node_uuid: string
257   @param target_node_uuid: used to override the node on which to remove the
258           disks
259   @rtype: boolean
260   @return: the success of the removal
261
262   """
263   logging.info("Removing block devices for instance %s", instance.name)
264
265   all_result = True
266   ports_to_release = set()
267   anno_disks = AnnotateDiskParams(instance, instance.disks, lu.cfg)
268   for (idx, device) in enumerate(anno_disks):
269     if target_node_uuid:
270       edata = [(target_node_uuid, device)]
271     else:
272       edata = device.ComputeNodeTree(instance.primary_node)
273     for node_uuid, disk in edata:
274       lu.cfg.SetDiskID(disk, node_uuid)
275       result = lu.rpc.call_blockdev_remove(node_uuid, disk)
276       if result.fail_msg:
277         lu.LogWarning("Could not remove disk %s on node %s,"
278                       " continuing anyway: %s", idx,
279                       lu.cfg.GetNodeName(node_uuid), result.fail_msg)
280         if not (result.offline and node_uuid != instance.primary_node):
281           all_result = False
282
283     # if this is a DRBD disk, return its port to the pool
284     if device.dev_type in constants.DTS_DRBD:
285       ports_to_release.add(device.logical_id[2])
286
287   if all_result or ignore_failures:
288     for port in ports_to_release:
289       lu.cfg.AddTcpUdpPort(port)
290
291   CheckDiskTemplateEnabled(lu.cfg.GetClusterInfo(), instance.disk_template)
292
293   if instance.disk_template in constants.DTS_FILEBASED:
294     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
295     if target_node_uuid:
296       tgt = target_node_uuid
297     else:
298       tgt = instance.primary_node
299     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
300     if result.fail_msg:
301       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
302                     file_storage_dir, lu.cfg.GetNodeName(tgt), result.fail_msg)
303       all_result = False
304
305   return all_result
306
307
308 def NICToTuple(lu, nic):
309   """Build a tupple of nic information.
310
311   @type lu:  L{LogicalUnit}
312   @param lu: the logical unit on whose behalf we execute
313   @type nic: L{objects.NIC}
314   @param nic: nic to convert to hooks tuple
315
316   """
317   cluster = lu.cfg.GetClusterInfo()
318   filled_params = cluster.SimpleFillNIC(nic.nicparams)
319   mode = filled_params[constants.NIC_MODE]
320   link = filled_params[constants.NIC_LINK]
321   netinfo = None
322   if nic.network:
323     nobj = lu.cfg.GetNetwork(nic.network)
324     netinfo = objects.Network.ToDict(nobj)
325   return (nic.name, nic.uuid, nic.ip, nic.mac, mode, link, nic.network, netinfo)
326
327
328 def NICListToTuple(lu, nics):
329   """Build a list of nic information tuples.
330
331   This list is suitable to be passed to _BuildInstanceHookEnv or as a return
332   value in LUInstanceQueryData.
333
334   @type lu:  L{LogicalUnit}
335   @param lu: the logical unit on whose behalf we execute
336   @type nics: list of L{objects.NIC}
337   @param nics: list of nics to convert to hooks tuples
338
339   """
340   hooks_nics = []
341   for nic in nics:
342     hooks_nics.append(NICToTuple(lu, nic))
343   return hooks_nics
344
345
346 def CopyLockList(names):
347   """Makes a copy of a list of lock names.
348
349   Handles L{locking.ALL_SET} correctly.
350
351   """
352   if names == locking.ALL_SET:
353     return locking.ALL_SET
354   else:
355     return names[:]
356
357
358 def ReleaseLocks(lu, level, names=None, keep=None):
359   """Releases locks owned by an LU.
360
361   @type lu: L{LogicalUnit}
362   @param level: Lock level
363   @type names: list or None
364   @param names: Names of locks to release
365   @type keep: list or None
366   @param keep: Names of locks to retain
367
368   """
369   assert not (keep is not None and names is not None), \
370          "Only one of the 'names' and the 'keep' parameters can be given"
371
372   if names is not None:
373     should_release = names.__contains__
374   elif keep:
375     should_release = lambda name: name not in keep
376   else:
377     should_release = None
378
379   owned = lu.owned_locks(level)
380   if not owned:
381     # Not owning any lock at this level, do nothing
382     pass
383
384   elif should_release:
385     retain = []
386     release = []
387
388     # Determine which locks to release
389     for name in owned:
390       if should_release(name):
391         release.append(name)
392       else:
393         retain.append(name)
394
395     assert len(lu.owned_locks(level)) == (len(retain) + len(release))
396
397     # Release just some locks
398     lu.glm.release(level, names=release)
399
400     assert frozenset(lu.owned_locks(level)) == frozenset(retain)
401   else:
402     # Release everything
403     lu.glm.release(level)
404
405     assert not lu.glm.is_owned(level), "No locks should be owned"
406
407
408 def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
409                                  target_group, cfg,
410                                  _compute_fn=ComputeIPolicyInstanceViolation):
411   """Compute if instance meets the specs of the new target group.
412
413   @param ipolicy: The ipolicy to verify
414   @param instance: The instance object to verify
415   @param current_group: The current group of the instance
416   @param target_group: The new group of the instance
417   @type cfg: L{config.ConfigWriter}
418   @param cfg: Cluster configuration
419   @param _compute_fn: The function to verify ipolicy (unittest only)
420   @see: L{ganeti.cmdlib.common.ComputeIPolicySpecViolation}
421
422   """
423   if current_group == target_group:
424     return []
425   else:
426     return _compute_fn(ipolicy, instance, cfg)
427
428
429 def CheckTargetNodeIPolicy(lu, ipolicy, instance, node, cfg, ignore=False,
430                            _compute_fn=_ComputeIPolicyNodeViolation):
431   """Checks that the target node is correct in terms of instance policy.
432
433   @param ipolicy: The ipolicy to verify
434   @param instance: The instance object to verify
435   @param node: The new node to relocate
436   @type cfg: L{config.ConfigWriter}
437   @param cfg: Cluster configuration
438   @param ignore: Ignore violations of the ipolicy
439   @param _compute_fn: The function to verify ipolicy (unittest only)
440   @see: L{ganeti.cmdlib.common.ComputeIPolicySpecViolation}
441
442   """
443   primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
444   res = _compute_fn(ipolicy, instance, primary_node.group, node.group, cfg)
445
446   if res:
447     msg = ("Instance does not meet target node group's (%s) instance"
448            " policy: %s") % (node.group, utils.CommaJoin(res))
449     if ignore:
450       lu.LogWarning(msg)
451     else:
452       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
453
454
455 def GetInstanceInfoText(instance):
456   """Compute that text that should be added to the disk's metadata.
457
458   """
459   return "originstname+%s" % instance.name
460
461
462 def CheckNodeFreeMemory(lu, node_uuid, reason, requested, hvname, hvparams):
463   """Checks if a node has enough free memory.
464
465   This function checks if a given node has the needed amount of free
466   memory. In case the node has less memory or we cannot get the
467   information from the node, this function raises an OpPrereqError
468   exception.
469
470   @type lu: C{LogicalUnit}
471   @param lu: a logical unit from which we get configuration data
472   @type node_uuid: C{str}
473   @param node_uuid: the node to check
474   @type reason: C{str}
475   @param reason: string to use in the error message
476   @type requested: C{int}
477   @param requested: the amount of memory in MiB to check for
478   @type hvname: string
479   @param hvname: the hypervisor's name
480   @type hvparams: dict of strings
481   @param hvparams: the hypervisor's parameters
482   @rtype: integer
483   @return: node current free memory
484   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
485       we cannot check the node
486
487   """
488   node_name = lu.cfg.GetNodeName(node_uuid)
489   nodeinfo = lu.rpc.call_node_info([node_uuid], None, [(hvname, hvparams)])
490   nodeinfo[node_uuid].Raise("Can't get data from node %s" % node_name,
491                             prereq=True, ecode=errors.ECODE_ENVIRON)
492   (_, _, (hv_info, )) = nodeinfo[node_uuid].payload
493
494   free_mem = hv_info.get("memory_free", None)
495   if not isinstance(free_mem, int):
496     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
497                                " was '%s'" % (node_name, free_mem),
498                                errors.ECODE_ENVIRON)
499   if requested > free_mem:
500     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
501                                " needed %s MiB, available %s MiB" %
502                                (node_name, reason, requested, free_mem),
503                                errors.ECODE_NORES)
504   return free_mem
505
506
507 def CheckInstanceBridgesExist(lu, instance, node_uuid=None):
508   """Check that the brigdes needed by an instance exist.
509
510   """
511   if node_uuid is None:
512     node_uuid = instance.primary_node
513   CheckNicsBridgesExist(lu, instance.nics, node_uuid)
514
515
516 def CheckNicsBridgesExist(lu, nics, node_uuid):
517   """Check that the brigdes needed by a list of nics exist.
518
519   """
520   cluster = lu.cfg.GetClusterInfo()
521   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in nics]
522   brlist = [params[constants.NIC_LINK] for params in paramslist
523             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
524   if brlist:
525     result = lu.rpc.call_bridges_exist(node_uuid, brlist)
526     result.Raise("Error checking bridges on destination node '%s'" %
527                  lu.cfg.GetNodeName(node_uuid), prereq=True,
528                  ecode=errors.ECODE_ENVIRON)
529
530
531 def CheckNodeHasOS(lu, node_uuid, os_name, force_variant):
532   """Ensure that a node supports a given OS.
533
534   @param lu: the LU on behalf of which we make the check
535   @param node_uuid: the node to check
536   @param os_name: the OS to query about
537   @param force_variant: whether to ignore variant errors
538   @raise errors.OpPrereqError: if the node is not supporting the OS
539
540   """
541   result = lu.rpc.call_os_get(node_uuid, os_name)
542   result.Raise("OS '%s' not in supported OS list for node %s" %
543                (os_name, lu.cfg.GetNodeName(node_uuid)),
544                prereq=True, ecode=errors.ECODE_INVAL)
545   if not force_variant:
546     _CheckOSVariant(result.payload, os_name)
547
548
549 def _CheckOSVariant(os_obj, name):
550   """Check whether an OS name conforms to the os variants specification.
551
552   @type os_obj: L{objects.OS}
553   @param os_obj: OS object to check
554   @type name: string
555   @param name: OS name passed by the user, to check for validity
556
557   """
558   variant = objects.OS.GetVariant(name)
559   if not os_obj.supported_variants:
560     if variant:
561       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
562                                  " passed)" % (os_obj.name, variant),
563                                  errors.ECODE_INVAL)
564     return
565   if not variant:
566     raise errors.OpPrereqError("OS name must include a variant",
567                                errors.ECODE_INVAL)
568
569   if variant not in os_obj.supported_variants:
570     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)