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