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"
38 # Key to store the cluster-wide run-time value of the exclusive storage flag
39 _EXCLUSIVE_STORAGE_KEY = "_exclusive_storage"
47 """Loads the passed configuration file.
50 global cfg # pylint: disable=W0603
52 cfg = serializer.LoadJson(utils.ReadFile(path))
58 if len(cfg["nodes"]) < 1:
59 raise qa_error.Error("Need at least one node")
60 if len(cfg["instances"]) < 1:
61 raise qa_error.Error("Need at least one instance")
62 if len(cfg["disk"]) != len(cfg["disk-growth"]):
63 raise qa_error.Error("Config options 'disk' and 'disk-growth' must have"
64 " the same number of items")
66 check = GetInstanceCheckScript()
70 except EnvironmentError, err:
71 raise qa_error.Error("Can't find instance check script '%s': %s" %
74 enabled_hv = frozenset(GetEnabledHypervisors())
76 raise qa_error.Error("No hypervisor is enabled")
78 difference = enabled_hv - constants.HYPER_TYPES
80 raise qa_error.Error("Unknown hypervisor(s) enabled: %s" %
81 utils.CommaJoin(difference))
84 def get(name, default=None):
85 return cfg.get(name, default)
89 def __init__(self, tests):
90 """Initializes this class.
92 @type tests: list or string
93 @param tests: List of test names
94 @see: L{TestEnabled} for details
100 def _MakeSequence(value):
101 """Make sequence of single argument.
103 If the single argument is not already a list or tuple, a list with the
104 argument as a single item is returned.
107 if isinstance(value, (list, tuple)):
113 def _TestEnabledInner(check_fn, names, fn):
114 """Evaluate test conditions.
116 @type check_fn: callable
117 @param check_fn: Callback to check whether a test is enabled
118 @type names: sequence or string
119 @param names: Test name(s)
121 @param fn: Aggregation function
123 @return: Whether test is enabled
126 names = _MakeSequence(names)
131 if isinstance(name, Either):
132 value = _TestEnabledInner(check_fn, name.tests, compat.any)
133 elif isinstance(name, (list, tuple)):
134 value = _TestEnabledInner(check_fn, name, compat.all)
136 value = check_fn(name)
143 def TestEnabled(tests, _cfg=None):
144 """Returns True if the given tests are enabled.
146 @param tests: A single test as a string, or a list of tests to check; can
147 contain L{Either} for OR conditions, AND is default
153 # Get settings for all tests
154 cfg_tests = _cfg.get("tests", {})
156 # Get default setting
157 default = cfg_tests.get("default", True)
159 return _TestEnabledInner(lambda name: cfg_tests.get(name, default),
163 def GetInstanceCheckScript():
164 """Returns path to instance check script or C{None}.
167 return cfg.get(_INSTANCE_CHECK_KEY, None)
170 def GetEnabledHypervisors():
171 """Returns list of enabled hypervisors.
177 value = cfg[_ENABLED_HV_KEY]
179 return [constants.DEFAULT_ENABLED_HYPERVISOR]
181 if isinstance(value, basestring):
182 # The configuration key ("enabled-hypervisors") implies there can be
183 # multiple values. Multiple hypervisors are comma-separated on the
184 # command line option to "gnt-cluster init", so we need to handle them
186 return value.split(",")
191 def GetDefaultHypervisor():
192 """Returns the default hypervisor to be used.
195 return GetEnabledHypervisors()[0]
198 def GetInstanceNicMac(inst, default=None):
199 """Returns MAC address for instance's network interface.
202 return inst.get("nic.mac/0", default)
206 return cfg["nodes"][0]
209 def AcquireInstance():
210 """Returns an instance which isn't in use.
213 # Filter out unwanted instances
214 tmp_flt = lambda inst: not inst.get("_used", False)
215 instances = filter(tmp_flt, cfg["instances"])
218 if len(instances) == 0:
219 raise qa_error.OutOfInstancesError("No instances left")
223 inst["_template"] = None
227 def ReleaseInstance(inst):
228 inst["_used"] = False
231 def GetInstanceTemplate(inst):
232 """Return the disk template of an instance.
235 templ = inst["_template"]
236 assert templ is not None
240 def SetInstanceTemplate(inst, template):
241 """Set the disk template for an instance.
244 inst["_template"] = template
247 def SetExclusiveStorage(value):
248 """Set the expected value of the exclusive_storage flag for the cluster.
251 cfg[_EXCLUSIVE_STORAGE_KEY] = bool(value)
254 def GetExclusiveStorage():
255 """Get the expected value of the exclusive_storage flag for the cluster.
258 val = cfg.get(_EXCLUSIVE_STORAGE_KEY)
259 assert val is not None
263 def IsTemplateSupported(templ):
264 """Is the given templated supported by the current configuration?
267 if GetExclusiveStorage():
268 return templ in constants.DTS_EXCL_STORAGE
273 def AcquireNode(exclude=None):
274 """Returns the least used node.
277 master = GetMasterNode()
279 # Filter out unwanted nodes
280 # TODO: Maybe combine filters
282 nodes = cfg["nodes"][:]
283 elif isinstance(exclude, (list, tuple)):
284 nodes = filter(lambda node: node not in exclude, cfg["nodes"])
286 nodes = filter(lambda node: node != exclude, cfg["nodes"])
288 tmp_flt = lambda node: node.get("_added", False) or node == master
289 nodes = filter(tmp_flt, nodes)
293 raise qa_error.OutOfNodesError("No nodes left")
295 # Get node with least number of uses
297 result = cmp(a.get("_count", 0), b.get("_count", 0))
299 result = cmp(a["primary"], b["primary"])
302 nodes.sort(cmp=compare)
305 node["_count"] = node.get("_count", 0) + 1
309 def AcquireManyNodes(num, exclude=None):
310 """Return the least used nodes.
313 @param num: Number of nodes; can be 0.
314 @type exclude: list of nodes or C{None}
315 @param exclude: nodes to be excluded from the choice
316 @rtype: list of nodes
317 @return: C{num} different nodes
323 elif isinstance(exclude, (list, tuple)):
324 # Don't modify the incoming argument
325 exclude = list(exclude)
330 for _ in range(0, num):
331 n = AcquireNode(exclude=exclude)
334 except qa_error.OutOfNodesError:
335 ReleaseManyNodes(nodes)
340 def ReleaseNode(node):
341 node["_count"] = node.get("_count", 0) - 1
344 def ReleaseManyNodes(nodes):