4 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
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.
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.
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
28 from ganeti import constants
29 from ganeti import utils
30 from ganeti import serializer
31 from ganeti import compat
36 _INSTANCE_CHECK_KEY = "instance-check"
37 _ENABLED_HV_KEY = "enabled-hypervisors"
39 #: QA configuration (L{_QaConfig})
43 class _QaConfig(object):
44 def __init__(self, data):
45 """Initializes instances of this class.
50 #: Cluster-wide run-time value of the exclusive storage flag
51 self._exclusive_storage = None
54 def Load(cls, filename):
55 """Loads a configuration file and produces a configuration object.
57 @type filename: string
58 @param filename: Path to configuration file
62 data = serializer.LoadJson(utils.ReadFile(filename))
70 """Validates loaded configuration data.
73 if not self.get("nodes"):
74 raise qa_error.Error("Need at least one node")
76 if not self.get("instances"):
77 raise qa_error.Error("Need at least one instance")
79 if (self.get("disk") is None or
80 self.get("disk-growth") is None or
81 len(self.get("disk")) != len(self.get("disk-growth"))):
82 raise qa_error.Error("Config options 'disk' and 'disk-growth' must exist"
83 " and have the same number of items")
85 check = self.GetInstanceCheckScript()
89 except EnvironmentError, err:
90 raise qa_error.Error("Can't find instance check script '%s': %s" %
93 enabled_hv = frozenset(self.GetEnabledHypervisors())
95 raise qa_error.Error("No hypervisor is enabled")
97 difference = enabled_hv - constants.HYPER_TYPES
99 raise qa_error.Error("Unknown hypervisor(s) enabled: %s" %
100 utils.CommaJoin(difference))
102 def __getitem__(self, name):
103 """Returns configuration value.
106 @param name: Name of configuration entry
109 return self._data[name]
111 def get(self, name, default=None):
112 """Returns configuration value.
115 @param name: Name of configuration entry
116 @param default: Default value
119 return self._data.get(name, default)
121 def GetMasterNode(self):
122 """Returns the default master node for the cluster.
125 return self["nodes"][0]
127 def GetInstanceCheckScript(self):
128 """Returns path to instance check script or C{None}.
131 return self._data.get(_INSTANCE_CHECK_KEY, None)
133 def GetEnabledHypervisors(self):
134 """Returns list of enabled hypervisors.
140 value = self._data[_ENABLED_HV_KEY]
142 return [constants.DEFAULT_ENABLED_HYPERVISOR]
146 elif isinstance(value, basestring):
147 # The configuration key ("enabled-hypervisors") implies there can be
148 # multiple values. Multiple hypervisors are comma-separated on the
149 # command line option to "gnt-cluster init", so we need to handle them
151 return value.split(",")
155 def GetDefaultHypervisor(self):
156 """Returns the default hypervisor to be used.
159 return self.GetEnabledHypervisors()[0]
161 def SetExclusiveStorage(self, value):
162 """Set the expected value of the C{exclusive_storage} flag for the cluster.
165 self._exclusive_storage = bool(value)
167 def GetExclusiveStorage(self):
168 """Get the expected value of the C{exclusive_storage} flag for the cluster.
171 value = self._exclusive_storage
172 assert value is not None
175 def IsTemplateSupported(self, templ):
176 """Is the given disk template supported by the current configuration?
179 if self.GetExclusiveStorage():
180 return templ in constants.DTS_EXCL_STORAGE
186 """Loads the passed configuration file.
189 global _config # pylint: disable=W0603
191 _config = _QaConfig.Load(path)
195 """Returns the configuration object.
199 raise RuntimeError("Configuration not yet loaded")
204 def get(name, default=None):
205 """Wrapper for L{_QaConfig.get}.
208 return GetConfig().get(name, default=default)
212 def __init__(self, tests):
213 """Initializes this class.
215 @type tests: list or string
216 @param tests: List of test names
217 @see: L{TestEnabled} for details
223 def _MakeSequence(value):
224 """Make sequence of single argument.
226 If the single argument is not already a list or tuple, a list with the
227 argument as a single item is returned.
230 if isinstance(value, (list, tuple)):
236 def _TestEnabledInner(check_fn, names, fn):
237 """Evaluate test conditions.
239 @type check_fn: callable
240 @param check_fn: Callback to check whether a test is enabled
241 @type names: sequence or string
242 @param names: Test name(s)
244 @param fn: Aggregation function
246 @return: Whether test is enabled
249 names = _MakeSequence(names)
254 if isinstance(name, Either):
255 value = _TestEnabledInner(check_fn, name.tests, compat.any)
256 elif isinstance(name, (list, tuple)):
257 value = _TestEnabledInner(check_fn, name, compat.all)
259 value = check_fn(name)
266 def TestEnabled(tests, _cfg=None):
267 """Returns True if the given tests are enabled.
269 @param tests: A single test as a string, or a list of tests to check; can
270 contain L{Either} for OR conditions, AND is default
278 # Get settings for all tests
279 cfg_tests = cfg.get("tests", {})
281 # Get default setting
282 default = cfg_tests.get("default", True)
284 return _TestEnabledInner(lambda name: cfg_tests.get(name, default),
288 def GetInstanceCheckScript(*args):
289 """Wrapper for L{_QaConfig.GetInstanceCheckScript}.
292 return GetConfig().GetInstanceCheckScript(*args)
295 def GetEnabledHypervisors(*args):
296 """Wrapper for L{_QaConfig.GetEnabledHypervisors}.
299 return GetConfig().GetEnabledHypervisors(*args)
302 def GetDefaultHypervisor(*args):
303 """Wrapper for L{_QaConfig.GetDefaultHypervisor}.
306 return GetConfig().GetDefaultHypervisor(*args)
309 def GetInstanceNicMac(inst, default=None):
310 """Returns MAC address for instance's network interface.
313 return inst.get("nic.mac/0", default)
317 """Wrapper for L{_QaConfig.GetMasterNode}.
320 return GetConfig().GetMasterNode()
323 def AcquireInstance():
324 """Returns an instance which isn't in use.
327 # Filter out unwanted instances
328 tmp_flt = lambda inst: not inst.get("_used", False)
329 instances = filter(tmp_flt, GetConfig()["instances"])
332 if len(instances) == 0:
333 raise qa_error.OutOfInstancesError("No instances left")
337 inst["_template"] = None
341 def ReleaseInstance(inst):
342 inst["_used"] = False
345 def GetInstanceTemplate(inst):
346 """Return the disk template of an instance.
349 templ = inst["_template"]
350 assert templ is not None
354 def SetInstanceTemplate(inst, template):
355 """Set the disk template for an instance.
358 inst["_template"] = template
361 def SetExclusiveStorage(value):
362 """Wrapper for L{_QaConfig.SetExclusiveStorage}.
365 return GetConfig().SetExclusiveStorage(value)
368 def GetExclusiveStorage():
369 """Wrapper for L{_QaConfig.GetExclusiveStorage}.
372 return GetConfig().GetExclusiveStorage()
375 def IsTemplateSupported(templ):
376 """Wrapper for L{_QaConfig.GetExclusiveStorage}.
379 return GetConfig().IsTemplateSupported(templ)
382 def AcquireNode(exclude=None):
383 """Returns the least used node.
386 master = GetMasterNode()
389 # Filter out unwanted nodes
390 # TODO: Maybe combine filters
392 nodes = cfg["nodes"][:]
393 elif isinstance(exclude, (list, tuple)):
394 nodes = filter(lambda node: node not in exclude, cfg["nodes"])
396 nodes = filter(lambda node: node != exclude, cfg["nodes"])
398 tmp_flt = lambda node: node.get("_added", False) or node == master
399 nodes = filter(tmp_flt, nodes)
403 raise qa_error.OutOfNodesError("No nodes left")
405 # Get node with least number of uses
407 result = cmp(a.get("_count", 0), b.get("_count", 0))
409 result = cmp(a["primary"], b["primary"])
412 nodes.sort(cmp=compare)
415 node["_count"] = node.get("_count", 0) + 1
419 def AcquireManyNodes(num, exclude=None):
420 """Return the least used nodes.
423 @param num: Number of nodes; can be 0.
424 @type exclude: list of nodes or C{None}
425 @param exclude: nodes to be excluded from the choice
426 @rtype: list of nodes
427 @return: C{num} different nodes
433 elif isinstance(exclude, (list, tuple)):
434 # Don't modify the incoming argument
435 exclude = list(exclude)
440 for _ in range(0, num):
441 n = AcquireNode(exclude=exclude)
444 except qa_error.OutOfNodesError:
445 ReleaseManyNodes(nodes)
450 def ReleaseNode(node):
451 node["_count"] = node.get("_count", 0) - 1
454 def ReleaseManyNodes(nodes):