cmdlib: Cleanup public/private functions
[ganeti-github.git] / 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)