Statistics
| Branch: | Tag: | Revision:

root / qa / qa_config.py @ 6d3d13ab

History | View | Annotate | Download (7.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 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
"""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
# Key to store the cluster-wide run-time value of the exclusive storage flag
39
_EXCLUSIVE_STORAGE_KEY = "_exclusive_storage"
40

    
41

    
42
cfg = {}
43
options = None
44

    
45

    
46
def Load(path):
47
  """Loads the passed configuration file.
48

49
  """
50
  global cfg # pylint: disable=W0603
51

    
52
  cfg = serializer.LoadJson(utils.ReadFile(path))
53

    
54
  Validate()
55

    
56

    
57
def Validate():
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")
65

    
66
  check = GetInstanceCheckScript()
67
  if check:
68
    try:
69
      os.stat(check)
70
    except EnvironmentError, err:
71
      raise qa_error.Error("Can't find instance check script '%s': %s" %
72
                           (check, err))
73

    
74
  enabled_hv = frozenset(GetEnabledHypervisors())
75
  if not enabled_hv:
76
    raise qa_error.Error("No hypervisor is enabled")
77

    
78
  difference = enabled_hv - constants.HYPER_TYPES
79
  if difference:
80
    raise qa_error.Error("Unknown hypervisor(s) enabled: %s" %
81
                         utils.CommaJoin(difference))
82

    
83

    
84
def get(name, default=None):
85
  return cfg.get(name, default)
86

    
87

    
88
class Either:
89
  def __init__(self, tests):
90
    """Initializes this class.
91

92
    @type tests: list or string
93
    @param tests: List of test names
94
    @see: L{TestEnabled} for details
95

96
    """
97
    self.tests = tests
98

    
99

    
100
def _MakeSequence(value):
101
  """Make sequence of single argument.
102

103
  If the single argument is not already a list or tuple, a list with the
104
  argument as a single item is returned.
105

106
  """
107
  if isinstance(value, (list, tuple)):
108
    return value
109
  else:
110
    return [value]
111

    
112

    
113
def _TestEnabledInner(check_fn, names, fn):
114
  """Evaluate test conditions.
115

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)
120
  @type fn: callable
121
  @param fn: Aggregation function
122
  @rtype: bool
123
  @return: Whether test is enabled
124

125
  """
126
  names = _MakeSequence(names)
127

    
128
  result = []
129

    
130
  for name in 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)
135
    else:
136
      value = check_fn(name)
137

    
138
    result.append(value)
139

    
140
  return fn(result)
141

    
142

    
143
def TestEnabled(tests, _cfg=None):
144
  """Returns True if the given tests are enabled.
145

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
148

149
  """
150
  if _cfg is None:
151
    _cfg = cfg
152

    
153
  # Get settings for all tests
154
  cfg_tests = _cfg.get("tests", {})
155

    
156
  # Get default setting
157
  default = cfg_tests.get("default", True)
158

    
159
  return _TestEnabledInner(lambda name: cfg_tests.get(name, default),
160
                           tests, compat.all)
161

    
162

    
163
def GetInstanceCheckScript():
164
  """Returns path to instance check script or C{None}.
165

166
  """
167
  return cfg.get(_INSTANCE_CHECK_KEY, None)
168

    
169

    
170
def GetEnabledHypervisors():
171
  """Returns list of enabled hypervisors.
172

173
  @rtype: list
174

175
  """
176
  try:
177
    value = cfg[_ENABLED_HV_KEY]
178
  except KeyError:
179
    return [constants.DEFAULT_ENABLED_HYPERVISOR]
180
  else:
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
185
      # equally here.
186
      return value.split(",")
187
    else:
188
      return value
189

    
190

    
191
def GetDefaultHypervisor():
192
  """Returns the default hypervisor to be used.
193

194
  """
195
  return GetEnabledHypervisors()[0]
196

    
197

    
198
def GetInstanceNicMac(inst, default=None):
199
  """Returns MAC address for instance's network interface.
200

201
  """
202
  return inst.get("nic.mac/0", default)
203

    
204

    
205
def GetMasterNode():
206
  return cfg["nodes"][0]
207

    
208

    
209
def AcquireInstance():
210
  """Returns an instance which isn't in use.
211

212
  """
213
  # Filter out unwanted instances
214
  tmp_flt = lambda inst: not inst.get("_used", False)
215
  instances = filter(tmp_flt, cfg["instances"])
216
  del tmp_flt
217

    
218
  if len(instances) == 0:
219
    raise qa_error.OutOfInstancesError("No instances left")
220

    
221
  inst = instances[0]
222
  inst["_used"] = True
223
  inst["_template"] = None
224
  return inst
225

    
226

    
227
def ReleaseInstance(inst):
228
  inst["_used"] = False
229

    
230

    
231
def GetInstanceTemplate(inst):
232
  """Return the disk template of an instance.
233

234
  """
235
  templ = inst["_template"]
236
  assert templ is not None
237
  return templ
238

    
239

    
240
def SetInstanceTemplate(inst, template):
241
  """Set the disk template for an instance.
242

243
  """
244
  inst["_template"] = template
245

    
246

    
247
def SetExclusiveStorage(value):
248
  """Set the expected value of the exclusive_storage flag for the cluster.
249

250
  """
251
  cfg[_EXCLUSIVE_STORAGE_KEY] = bool(value)
252

    
253

    
254
def GetExclusiveStorage():
255
  """Get the expected value of the exclusive_storage flag for the cluster.
256

257
  """
258
  val = cfg.get(_EXCLUSIVE_STORAGE_KEY)
259
  assert val is not None
260
  return val
261

    
262

    
263
def IsTemplateSupported(templ):
264
  """Is the given templated supported by the current configuration?
265

266
  """
267
  if GetExclusiveStorage():
268
    return templ in constants.DTS_EXCL_STORAGE
269
  else:
270
    return True
271

    
272

    
273
def AcquireNode(exclude=None):
274
  """Returns the least used node.
275

276
  """
277
  master = GetMasterNode()
278

    
279
  # Filter out unwanted nodes
280
  # TODO: Maybe combine filters
281
  if exclude is None:
282
    nodes = cfg["nodes"][:]
283
  elif isinstance(exclude, (list, tuple)):
284
    nodes = filter(lambda node: node not in exclude, cfg["nodes"])
285
  else:
286
    nodes = filter(lambda node: node != exclude, cfg["nodes"])
287

    
288
  tmp_flt = lambda node: node.get("_added", False) or node == master
289
  nodes = filter(tmp_flt, nodes)
290
  del tmp_flt
291

    
292
  if len(nodes) == 0:
293
    raise qa_error.OutOfNodesError("No nodes left")
294

    
295
  # Get node with least number of uses
296
  def compare(a, b):
297
    result = cmp(a.get("_count", 0), b.get("_count", 0))
298
    if result == 0:
299
      result = cmp(a["primary"], b["primary"])
300
    return result
301

    
302
  nodes.sort(cmp=compare)
303

    
304
  node = nodes[0]
305
  node["_count"] = node.get("_count", 0) + 1
306
  return node
307

    
308

    
309
def AcquireManyNodes(num, exclude=None):
310
  """Return the least used nodes.
311

312
  @type num: int
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
318

319
  """
320
  nodes = []
321
  if exclude is None:
322
    exclude = []
323
  elif isinstance(exclude, (list, tuple)):
324
    # Don't modify the incoming argument
325
    exclude = list(exclude)
326
  else:
327
    exclude = [exclude]
328

    
329
  try:
330
    for _ in range(0, num):
331
      n = AcquireNode(exclude=exclude)
332
      nodes.append(n)
333
      exclude.append(n)
334
  except qa_error.OutOfNodesError:
335
    ReleaseManyNodes(nodes)
336
    raise
337
  return nodes
338

    
339

    
340
def ReleaseNode(node):
341
  node["_count"] = node.get("_count", 0) - 1
342

    
343

    
344
def ReleaseManyNodes(nodes):
345
  for n in nodes:
346
    ReleaseNode(n)