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