Statistics
| Branch: | Tag: | Revision:

root / qa / qa_config.py @ 8a96c5a6

History | View | Annotate | Download (9.9 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

    
39
#: Cluster-wide run-time value of the exclusive storage flag
40
_exclusive_storage = None
41

    
42

    
43
#: QA configuration (L{_QaConfig})
44
_config = None
45

    
46

    
47
class _QaConfig(object):
48
  def __init__(self, data):
49
    """Initializes instances of this class.
50

51
    """
52
    self._data = data
53

    
54
  @classmethod
55
  def Load(cls, filename):
56
    """Loads a configuration file and produces a configuration object.
57

58
    @type filename: string
59
    @param filename: Path to configuration file
60
    @rtype: L{_QaConfig}
61

62
    """
63
    data = serializer.LoadJson(utils.ReadFile(filename))
64

    
65
    result = cls(data)
66
    result.Validate()
67

    
68
    return result
69

    
70
  def Validate(self):
71
    """Validates loaded configuration data.
72

73
    """
74
    if not self.get("nodes"):
75
      raise qa_error.Error("Need at least one node")
76

    
77
    if not self.get("instances"):
78
      raise qa_error.Error("Need at least one instance")
79

    
80
    if (self.get("disk") is None or
81
        self.get("disk-growth") is None or
82
        len(self.get("disk")) != len(self.get("disk-growth"))):
83
      raise qa_error.Error("Config options 'disk' and 'disk-growth' must exist"
84
                           " and have the same number of items")
85

    
86
    check = self.GetInstanceCheckScript()
87
    if check:
88
      try:
89
        os.stat(check)
90
      except EnvironmentError, err:
91
        raise qa_error.Error("Can't find instance check script '%s': %s" %
92
                             (check, err))
93

    
94
    enabled_hv = frozenset(self.GetEnabledHypervisors())
95
    if not enabled_hv:
96
      raise qa_error.Error("No hypervisor is enabled")
97

    
98
    difference = enabled_hv - constants.HYPER_TYPES
99
    if difference:
100
      raise qa_error.Error("Unknown hypervisor(s) enabled: %s" %
101
                           utils.CommaJoin(difference))
102

    
103
  def __getitem__(self, name):
104
    """Returns configuration value.
105

106
    @type name: string
107
    @param name: Name of configuration entry
108

109
    """
110
    return self._data[name]
111

    
112
  def get(self, name, default=None):
113
    """Returns configuration value.
114

115
    @type name: string
116
    @param name: Name of configuration entry
117
    @param default: Default value
118

119
    """
120
    return self._data.get(name, default)
121

    
122
  def GetMasterNode(self):
123
    """Returns the default master node for the cluster.
124

125
    """
126
    return self["nodes"][0]
127

    
128
  def GetInstanceCheckScript(self):
129
    """Returns path to instance check script or C{None}.
130

131
    """
132
    return self._data.get(_INSTANCE_CHECK_KEY, None)
133

    
134
  def GetEnabledHypervisors(self):
135
    """Returns list of enabled hypervisors.
136

137
    @rtype: list
138

139
    """
140
    try:
141
      value = self._data[_ENABLED_HV_KEY]
142
    except KeyError:
143
      return [constants.DEFAULT_ENABLED_HYPERVISOR]
144
    else:
145
      if value is None:
146
        return []
147
      elif isinstance(value, basestring):
148
        # The configuration key ("enabled-hypervisors") implies there can be
149
        # multiple values. Multiple hypervisors are comma-separated on the
150
        # command line option to "gnt-cluster init", so we need to handle them
151
        # equally here.
152
        return value.split(",")
153
      else:
154
        return value
155

    
156
  def GetDefaultHypervisor(self):
157
    """Returns the default hypervisor to be used.
158

159
    """
160
    return self.GetEnabledHypervisors()[0]
161

    
162

    
163
def Load(path):
164
  """Loads the passed configuration file.
165

166
  """
167
  global _config # pylint: disable=W0603
168

    
169
  _config = _QaConfig.Load(path)
170

    
171

    
172
def GetConfig():
173
  """Returns the configuration object.
174

175
  """
176
  if _config is None:
177
    raise RuntimeError("Configuration not yet loaded")
178

    
179
  return _config
180

    
181

    
182
def get(name, default=None):
183
  """Wrapper for L{_QaConfig.get}.
184

185
  """
186
  return GetConfig().get(name, default=default)
187

    
188

    
189
class Either:
190
  def __init__(self, tests):
191
    """Initializes this class.
192

193
    @type tests: list or string
194
    @param tests: List of test names
195
    @see: L{TestEnabled} for details
196

197
    """
198
    self.tests = tests
199

    
200

    
201
def _MakeSequence(value):
202
  """Make sequence of single argument.
203

204
  If the single argument is not already a list or tuple, a list with the
205
  argument as a single item is returned.
206

207
  """
208
  if isinstance(value, (list, tuple)):
209
    return value
210
  else:
211
    return [value]
212

    
213

    
214
def _TestEnabledInner(check_fn, names, fn):
215
  """Evaluate test conditions.
216

217
  @type check_fn: callable
218
  @param check_fn: Callback to check whether a test is enabled
219
  @type names: sequence or string
220
  @param names: Test name(s)
221
  @type fn: callable
222
  @param fn: Aggregation function
223
  @rtype: bool
224
  @return: Whether test is enabled
225

226
  """
227
  names = _MakeSequence(names)
228

    
229
  result = []
230

    
231
  for name in names:
232
    if isinstance(name, Either):
233
      value = _TestEnabledInner(check_fn, name.tests, compat.any)
234
    elif isinstance(name, (list, tuple)):
235
      value = _TestEnabledInner(check_fn, name, compat.all)
236
    else:
237
      value = check_fn(name)
238

    
239
    result.append(value)
240

    
241
  return fn(result)
242

    
243

    
244
def TestEnabled(tests, _cfg=None):
245
  """Returns True if the given tests are enabled.
246

247
  @param tests: A single test as a string, or a list of tests to check; can
248
    contain L{Either} for OR conditions, AND is default
249

250
  """
251
  if _cfg is None:
252
    cfg = GetConfig()
253
  else:
254
    cfg = _cfg
255

    
256
  # Get settings for all tests
257
  cfg_tests = cfg.get("tests", {})
258

    
259
  # Get default setting
260
  default = cfg_tests.get("default", True)
261

    
262
  return _TestEnabledInner(lambda name: cfg_tests.get(name, default),
263
                           tests, compat.all)
264

    
265

    
266
def GetInstanceCheckScript(*args):
267
  """Wrapper for L{_QaConfig.GetInstanceCheckScript}.
268

269
  """
270
  return GetConfig().GetInstanceCheckScript(*args)
271

    
272

    
273
def GetEnabledHypervisors(*args):
274
  """Wrapper for L{_QaConfig.GetEnabledHypervisors}.
275

276
  """
277
  return GetConfig().GetEnabledHypervisors(*args)
278

    
279

    
280
def GetDefaultHypervisor(*args):
281
  """Wrapper for L{_QaConfig.GetDefaultHypervisor}.
282

283
  """
284
  return GetConfig().GetDefaultHypervisor(*args)
285

    
286

    
287
def GetInstanceNicMac(inst, default=None):
288
  """Returns MAC address for instance's network interface.
289

290
  """
291
  return inst.get("nic.mac/0", default)
292

    
293

    
294
def GetMasterNode():
295
  """Wrapper for L{_QaConfig.GetMasterNode}.
296

297
  """
298
  return GetConfig().GetMasterNode()
299

    
300

    
301
def AcquireInstance():
302
  """Returns an instance which isn't in use.
303

304
  """
305
  # Filter out unwanted instances
306
  tmp_flt = lambda inst: not inst.get("_used", False)
307
  instances = filter(tmp_flt, GetConfig()["instances"])
308
  del tmp_flt
309

    
310
  if len(instances) == 0:
311
    raise qa_error.OutOfInstancesError("No instances left")
312

    
313
  inst = instances[0]
314
  inst["_used"] = True
315
  inst["_template"] = None
316
  return inst
317

    
318

    
319
def ReleaseInstance(inst):
320
  inst["_used"] = False
321

    
322

    
323
def GetInstanceTemplate(inst):
324
  """Return the disk template of an instance.
325

326
  """
327
  templ = inst["_template"]
328
  assert templ is not None
329
  return templ
330

    
331

    
332
def SetInstanceTemplate(inst, template):
333
  """Set the disk template for an instance.
334

335
  """
336
  inst["_template"] = template
337

    
338

    
339
def SetExclusiveStorage(value):
340
  """Set the expected value of the exclusive_storage flag for the cluster.
341

342
  """
343
  global _exclusive_storage # pylint: disable=W0603
344

    
345
  _exclusive_storage = bool(value)
346

    
347

    
348
def GetExclusiveStorage():
349
  """Get the expected value of the exclusive_storage flag for the cluster.
350

351
  """
352
  val = _exclusive_storage
353
  assert val is not None
354
  return val
355

    
356

    
357
def IsTemplateSupported(templ):
358
  """Is the given disk template supported by the current configuration?
359

360
  """
361
  if GetExclusiveStorage():
362
    return templ in constants.DTS_EXCL_STORAGE
363
  else:
364
    return True
365

    
366

    
367
def AcquireNode(exclude=None):
368
  """Returns the least used node.
369

370
  """
371
  master = GetMasterNode()
372
  cfg = GetConfig()
373

    
374
  # Filter out unwanted nodes
375
  # TODO: Maybe combine filters
376
  if exclude is None:
377
    nodes = cfg["nodes"][:]
378
  elif isinstance(exclude, (list, tuple)):
379
    nodes = filter(lambda node: node not in exclude, cfg["nodes"])
380
  else:
381
    nodes = filter(lambda node: node != exclude, cfg["nodes"])
382

    
383
  tmp_flt = lambda node: node.get("_added", False) or node == master
384
  nodes = filter(tmp_flt, nodes)
385
  del tmp_flt
386

    
387
  if len(nodes) == 0:
388
    raise qa_error.OutOfNodesError("No nodes left")
389

    
390
  # Get node with least number of uses
391
  def compare(a, b):
392
    result = cmp(a.get("_count", 0), b.get("_count", 0))
393
    if result == 0:
394
      result = cmp(a["primary"], b["primary"])
395
    return result
396

    
397
  nodes.sort(cmp=compare)
398

    
399
  node = nodes[0]
400
  node["_count"] = node.get("_count", 0) + 1
401
  return node
402

    
403

    
404
def AcquireManyNodes(num, exclude=None):
405
  """Return the least used nodes.
406

407
  @type num: int
408
  @param num: Number of nodes; can be 0.
409
  @type exclude: list of nodes or C{None}
410
  @param exclude: nodes to be excluded from the choice
411
  @rtype: list of nodes
412
  @return: C{num} different nodes
413

414
  """
415
  nodes = []
416
  if exclude is None:
417
    exclude = []
418
  elif isinstance(exclude, (list, tuple)):
419
    # Don't modify the incoming argument
420
    exclude = list(exclude)
421
  else:
422
    exclude = [exclude]
423

    
424
  try:
425
    for _ in range(0, num):
426
      n = AcquireNode(exclude=exclude)
427
      nodes.append(n)
428
      exclude.append(n)
429
  except qa_error.OutOfNodesError:
430
    ReleaseManyNodes(nodes)
431
    raise
432
  return nodes
433

    
434

    
435
def ReleaseNode(node):
436
  node["_count"] = node.get("_count", 0) - 1
437

    
438

    
439
def ReleaseManyNodes(nodes):
440
  for n in nodes:
441
    ReleaseNode(n)