fd8cd261412d0d2385b86985a8d41ec7d06704c9
[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
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
451
452
453 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
454   """Checks if a node has enough free memory.
455
456   This function checks if a given node has the needed amount of free
457   memory. In case the node has less memory or we cannot get the
458   information from the node, this function raises an OpPrereqError
459   exception.
460
461   @type lu: C{LogicalUnit}
462   @param lu: a logical unit from which we get configuration data
463   @type node: C{str}
464   @param node: the node to check
465   @type reason: C{str}
466   @param reason: string to use in the error message
467   @type requested: C{int}
468   @param requested: the amount of memory in MiB to check for
469   @type hypervisor_name: C{str}
470   @param hypervisor_name: the hypervisor to ask for memory stats
471   @rtype: integer
472   @return: node current free memory
473   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
474       we cannot check the node
475
476   """
477   nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name], False)
478   nodeinfo[node].Raise("Can't get data from node %s" % node,
479                        prereq=True, ecode=errors.ECODE_ENVIRON)
480   (_, _, (hv_info, )) = nodeinfo[node].payload
481
482   free_mem = hv_info.get("memory_free", None)
483   if not isinstance(free_mem, int):
484     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
485                                " was '%s'" % (node, free_mem),
486                                errors.ECODE_ENVIRON)
487   if requested > free_mem:
488     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
489                                " needed %s MiB, available %s MiB" %
490                                (node, reason, requested, free_mem),
491                                errors.ECODE_NORES)
492   return free_mem
493
494
495 def _CheckInstanceBridgesExist(lu, instance, node=None):
496   """Check that the brigdes needed by an instance exist.
497
498   """
499   if node is None:
500     node = instance.primary_node
501   _CheckNicsBridgesExist(lu, instance.nics, node)
502
503
504 def _CheckNicsBridgesExist(lu, target_nics, target_node):
505   """Check that the brigdes needed by a list of nics exist.
506
507   """
508   cluster = lu.cfg.GetClusterInfo()
509   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
510   brlist = [params[constants.NIC_LINK] for params in paramslist
511             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
512   if brlist:
513     result = lu.rpc.call_bridges_exist(target_node, brlist)
514     result.Raise("Error checking bridges on destination node '%s'" %
515                  target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
516
517
518 def _CheckNodeHasOS(lu, node, os_name, force_variant):
519   """Ensure that a node supports a given OS.
520
521   @param lu: the LU on behalf of which we make the check
522   @param node: the node to check
523   @param os_name: the OS to query about
524   @param force_variant: whether to ignore variant errors
525   @raise errors.OpPrereqError: if the node is not supporting the OS
526
527   """
528   result = lu.rpc.call_os_get(node, os_name)
529   result.Raise("OS '%s' not in supported OS list for node %s" %
530                (os_name, node),
531                prereq=True, ecode=errors.ECODE_INVAL)
532   if not force_variant:
533     _CheckOSVariant(result.payload, os_name)
534
535
536 def _CheckOSVariant(os_obj, name):
537   """Check whether an OS name conforms to the os variants specification.
538
539   @type os_obj: L{objects.OS}
540   @param os_obj: OS object to check
541   @type name: string
542   @param name: OS name passed by the user, to check for validity
543
544   """
545   variant = objects.OS.GetVariant(name)
546   if not os_obj.supported_variants:
547     if variant:
548       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
549                                  " passed)" % (os_obj.name, variant),
550                                  errors.ECODE_INVAL)
551     return
552   if not variant:
553     raise errors.OpPrereqError("OS name must include a variant",
554                                errors.ECODE_INVAL)
555
556   if variant not in os_obj.supported_variants:
557     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)