Locking related fixes for networks
[ganeti-local] / qa / qa_config.py
1 #
2 #
3
4 # Copyright (C) 2007, 2011, 2012 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 """QA configuration.
23
24 """
25
26 import os
27
28 from ganeti import constants
29 from ganeti import utils
30 from ganeti import serializer
31 from ganeti import compat
32
33 import qa_error
34
35
36 _INSTANCE_CHECK_KEY = "instance-check"
37 _ENABLED_HV_KEY = "enabled-hypervisors"
38
39
40 cfg = None
41 options = None
42
43
44 def Load(path):
45   """Loads the passed configuration file.
46
47   """
48   global cfg # pylint: disable=W0603
49
50   cfg = serializer.LoadJson(utils.ReadFile(path))
51
52   Validate()
53
54
55 def Validate():
56   if len(cfg["nodes"]) < 1:
57     raise qa_error.Error("Need at least one node")
58   if len(cfg["instances"]) < 1:
59     raise qa_error.Error("Need at least one instance")
60   if len(cfg["disk"]) != len(cfg["disk-growth"]):
61     raise qa_error.Error("Config options 'disk' and 'disk-growth' must have"
62                          " the same number of items")
63
64   check = GetInstanceCheckScript()
65   if check:
66     try:
67       os.stat(check)
68     except EnvironmentError, err:
69       raise qa_error.Error("Can't find instance check script '%s': %s" %
70                            (check, err))
71
72   enabled_hv = frozenset(GetEnabledHypervisors())
73   if not enabled_hv:
74     raise qa_error.Error("No hypervisor is enabled")
75
76   difference = enabled_hv - constants.HYPER_TYPES
77   if difference:
78     raise qa_error.Error("Unknown hypervisor(s) enabled: %s" %
79                          utils.CommaJoin(difference))
80
81
82 def get(name, default=None):
83   return cfg.get(name, default) # pylint: disable=E1103
84
85
86 class Either:
87   def __init__(self, tests):
88     """Initializes this class.
89
90     @type tests: list or string
91     @param tests: List of test names
92     @see: L{TestEnabled} for details
93
94     """
95     self.tests = tests
96
97
98 def _MakeSequence(value):
99   """Make sequence of single argument.
100
101   If the single argument is not already a list or tuple, a list with the
102   argument as a single item is returned.
103
104   """
105   if isinstance(value, (list, tuple)):
106     return value
107   else:
108     return [value]
109
110
111 def _TestEnabledInner(check_fn, names, fn):
112   """Evaluate test conditions.
113
114   @type check_fn: callable
115   @param check_fn: Callback to check whether a test is enabled
116   @type names: sequence or string
117   @param names: Test name(s)
118   @type fn: callable
119   @param fn: Aggregation function
120   @rtype: bool
121   @return: Whether test is enabled
122
123   """
124   names = _MakeSequence(names)
125
126   result = []
127
128   for name in names:
129     if isinstance(name, Either):
130       value = _TestEnabledInner(check_fn, name.tests, compat.any)
131     elif isinstance(name, (list, tuple)):
132       value = _TestEnabledInner(check_fn, name, compat.all)
133     else:
134       value = check_fn(name)
135
136     result.append(value)
137
138   return fn(result)
139
140
141 def TestEnabled(tests, _cfg=None):
142   """Returns True if the given tests are enabled.
143
144   @param tests: A single test as a string, or a list of tests to check; can
145     contain L{Either} for OR conditions, AND is default
146
147   """
148   if _cfg is None:
149     _cfg = cfg
150
151   # Get settings for all tests
152   cfg_tests = _cfg.get("tests", {}) # pylint: disable=E1103
153
154   # Get default setting
155   default = cfg_tests.get("default", True)
156
157   return _TestEnabledInner(lambda name: cfg_tests.get(name, default),
158                            tests, compat.all)
159
160
161 def GetInstanceCheckScript():
162   """Returns path to instance check script or C{None}.
163
164   """
165   return cfg.get(_INSTANCE_CHECK_KEY, None) # pylint: disable=E1103
166
167
168 def GetEnabledHypervisors():
169   """Returns list of enabled hypervisors.
170
171   @rtype: list
172
173   """
174   try:
175     value = cfg[_ENABLED_HV_KEY]
176   except KeyError:
177     return [constants.DEFAULT_ENABLED_HYPERVISOR]
178   else:
179     if isinstance(value, basestring):
180       # The configuration key ("enabled-hypervisors") implies there can be
181       # multiple values. Multiple hypervisors are comma-separated on the
182       # command line option to "gnt-cluster init", so we need to handle them
183       # equally here.
184       return value.split(",")
185     else:
186       return value
187
188
189 def GetDefaultHypervisor():
190   """Returns the default hypervisor to be used.
191
192   """
193   return GetEnabledHypervisors()[0]
194
195
196 def GetInstanceNicMac(inst, default=None):
197   """Returns MAC address for instance's network interface.
198
199   """
200   return inst.get("nic.mac/0", default)
201
202
203 def GetMasterNode():
204   return cfg["nodes"][0]
205
206
207 def AcquireInstance():
208   """Returns an instance which isn't in use.
209
210   """
211   # Filter out unwanted instances
212   tmp_flt = lambda inst: not inst.get("_used", False)
213   instances = filter(tmp_flt, cfg["instances"])
214   del tmp_flt
215
216   if len(instances) == 0:
217     raise qa_error.OutOfInstancesError("No instances left")
218
219   inst = instances[0]
220   inst["_used"] = True
221   return inst
222
223
224 def ReleaseInstance(inst):
225   inst["_used"] = False
226
227
228 def AcquireNode(exclude=None):
229   """Returns the least used node.
230
231   """
232   master = GetMasterNode()
233
234   # Filter out unwanted nodes
235   # TODO: Maybe combine filters
236   if exclude is None:
237     nodes = cfg["nodes"][:]
238   elif isinstance(exclude, (list, tuple)):
239     nodes = filter(lambda node: node not in exclude, cfg["nodes"])
240   else:
241     nodes = filter(lambda node: node != exclude, cfg["nodes"])
242
243   tmp_flt = lambda node: node.get("_added", False) or node == master
244   nodes = filter(tmp_flt, nodes)
245   del tmp_flt
246
247   if len(nodes) == 0:
248     raise qa_error.OutOfNodesError("No nodes left")
249
250   # Get node with least number of uses
251   def compare(a, b):
252     result = cmp(a.get("_count", 0), b.get("_count", 0))
253     if result == 0:
254       result = cmp(a["primary"], b["primary"])
255     return result
256
257   nodes.sort(cmp=compare)
258
259   node = nodes[0]
260   node["_count"] = node.get("_count", 0) + 1
261   return node
262
263
264 def ReleaseNode(node):
265   node["_count"] = node.get("_count", 0) - 1