Statistics
| Branch: | Tag: | Revision:

root / qa / qa_config.py @ a08e181f

History | View | Annotate | Download (10.3 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
#: QA configuration (L{_QaConfig})
40
_config = None
41

    
42

    
43
class _QaConfig(object):
44
  def __init__(self, data):
45
    """Initializes instances of this class.
46

47
    """
48
    self._data = data
49

    
50
    #: Cluster-wide run-time value of the exclusive storage flag
51
    self._exclusive_storage = None
52

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

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

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

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

    
67
    return result
68

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

136
    @rtype: list
137

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

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

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

    
161
  def SetExclusiveStorage(self, value):
162
    """Set the expected value of the C{exclusive_storage} flag for the cluster.
163

164
    """
165
    self._exclusive_storage = bool(value)
166

    
167
  def GetExclusiveStorage(self):
168
    """Get the expected value of the C{exclusive_storage} flag for the cluster.
169

170
    """
171
    value = self._exclusive_storage
172
    assert value is not None
173
    return value
174

    
175
  def IsTemplateSupported(self, templ):
176
    """Is the given disk template supported by the current configuration?
177

178
    """
179
    if self.GetExclusiveStorage():
180
      return templ in constants.DTS_EXCL_STORAGE
181
    else:
182
      return True
183

    
184

    
185
def Load(path):
186
  """Loads the passed configuration file.
187

188
  """
189
  global _config # pylint: disable=W0603
190

    
191
  _config = _QaConfig.Load(path)
192

    
193

    
194
def GetConfig():
195
  """Returns the configuration object.
196

197
  """
198
  if _config is None:
199
    raise RuntimeError("Configuration not yet loaded")
200

    
201
  return _config
202

    
203

    
204
def get(name, default=None):
205
  """Wrapper for L{_QaConfig.get}.
206

207
  """
208
  return GetConfig().get(name, default=default)
209

    
210

    
211
class Either:
212
  def __init__(self, tests):
213
    """Initializes this class.
214

215
    @type tests: list or string
216
    @param tests: List of test names
217
    @see: L{TestEnabled} for details
218

219
    """
220
    self.tests = tests
221

    
222

    
223
def _MakeSequence(value):
224
  """Make sequence of single argument.
225

226
  If the single argument is not already a list or tuple, a list with the
227
  argument as a single item is returned.
228

229
  """
230
  if isinstance(value, (list, tuple)):
231
    return value
232
  else:
233
    return [value]
234

    
235

    
236
def _TestEnabledInner(check_fn, names, fn):
237
  """Evaluate test conditions.
238

239
  @type check_fn: callable
240
  @param check_fn: Callback to check whether a test is enabled
241
  @type names: sequence or string
242
  @param names: Test name(s)
243
  @type fn: callable
244
  @param fn: Aggregation function
245
  @rtype: bool
246
  @return: Whether test is enabled
247

248
  """
249
  names = _MakeSequence(names)
250

    
251
  result = []
252

    
253
  for name in names:
254
    if isinstance(name, Either):
255
      value = _TestEnabledInner(check_fn, name.tests, compat.any)
256
    elif isinstance(name, (list, tuple)):
257
      value = _TestEnabledInner(check_fn, name, compat.all)
258
    else:
259
      value = check_fn(name)
260

    
261
    result.append(value)
262

    
263
  return fn(result)
264

    
265

    
266
def TestEnabled(tests, _cfg=None):
267
  """Returns True if the given tests are enabled.
268

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

272
  """
273
  if _cfg is None:
274
    cfg = GetConfig()
275
  else:
276
    cfg = _cfg
277

    
278
  # Get settings for all tests
279
  cfg_tests = cfg.get("tests", {})
280

    
281
  # Get default setting
282
  default = cfg_tests.get("default", True)
283

    
284
  return _TestEnabledInner(lambda name: cfg_tests.get(name, default),
285
                           tests, compat.all)
286

    
287

    
288
def GetInstanceCheckScript(*args):
289
  """Wrapper for L{_QaConfig.GetInstanceCheckScript}.
290

291
  """
292
  return GetConfig().GetInstanceCheckScript(*args)
293

    
294

    
295
def GetEnabledHypervisors(*args):
296
  """Wrapper for L{_QaConfig.GetEnabledHypervisors}.
297

298
  """
299
  return GetConfig().GetEnabledHypervisors(*args)
300

    
301

    
302
def GetDefaultHypervisor(*args):
303
  """Wrapper for L{_QaConfig.GetDefaultHypervisor}.
304

305
  """
306
  return GetConfig().GetDefaultHypervisor(*args)
307

    
308

    
309
def GetInstanceNicMac(inst, default=None):
310
  """Returns MAC address for instance's network interface.
311

312
  """
313
  return inst.get("nic.mac/0", default)
314

    
315

    
316
def GetMasterNode():
317
  """Wrapper for L{_QaConfig.GetMasterNode}.
318

319
  """
320
  return GetConfig().GetMasterNode()
321

    
322

    
323
def AcquireInstance():
324
  """Returns an instance which isn't in use.
325

326
  """
327
  # Filter out unwanted instances
328
  tmp_flt = lambda inst: not inst.get("_used", False)
329
  instances = filter(tmp_flt, GetConfig()["instances"])
330
  del tmp_flt
331

    
332
  if len(instances) == 0:
333
    raise qa_error.OutOfInstancesError("No instances left")
334

    
335
  inst = instances[0]
336
  inst["_used"] = True
337
  inst["_template"] = None
338
  return inst
339

    
340

    
341
def ReleaseInstance(inst):
342
  inst["_used"] = False
343

    
344

    
345
def GetInstanceTemplate(inst):
346
  """Return the disk template of an instance.
347

348
  """
349
  templ = inst["_template"]
350
  assert templ is not None
351
  return templ
352

    
353

    
354
def SetInstanceTemplate(inst, template):
355
  """Set the disk template for an instance.
356

357
  """
358
  inst["_template"] = template
359

    
360

    
361
def SetExclusiveStorage(value):
362
  """Wrapper for L{_QaConfig.SetExclusiveStorage}.
363

364
  """
365
  return GetConfig().SetExclusiveStorage(value)
366

    
367

    
368
def GetExclusiveStorage():
369
  """Wrapper for L{_QaConfig.GetExclusiveStorage}.
370

371
  """
372
  return GetConfig().GetExclusiveStorage()
373

    
374

    
375
def IsTemplateSupported(templ):
376
  """Wrapper for L{_QaConfig.GetExclusiveStorage}.
377

378
  """
379
  return GetConfig().IsTemplateSupported(templ)
380

    
381

    
382
def AcquireNode(exclude=None):
383
  """Returns the least used node.
384

385
  """
386
  master = GetMasterNode()
387
  cfg = GetConfig()
388

    
389
  # Filter out unwanted nodes
390
  # TODO: Maybe combine filters
391
  if exclude is None:
392
    nodes = cfg["nodes"][:]
393
  elif isinstance(exclude, (list, tuple)):
394
    nodes = filter(lambda node: node not in exclude, cfg["nodes"])
395
  else:
396
    nodes = filter(lambda node: node != exclude, cfg["nodes"])
397

    
398
  tmp_flt = lambda node: node.get("_added", False) or node == master
399
  nodes = filter(tmp_flt, nodes)
400
  del tmp_flt
401

    
402
  if len(nodes) == 0:
403
    raise qa_error.OutOfNodesError("No nodes left")
404

    
405
  # Get node with least number of uses
406
  def compare(a, b):
407
    result = cmp(a.get("_count", 0), b.get("_count", 0))
408
    if result == 0:
409
      result = cmp(a["primary"], b["primary"])
410
    return result
411

    
412
  nodes.sort(cmp=compare)
413

    
414
  node = nodes[0]
415
  node["_count"] = node.get("_count", 0) + 1
416
  return node
417

    
418

    
419
def AcquireManyNodes(num, exclude=None):
420
  """Return the least used nodes.
421

422
  @type num: int
423
  @param num: Number of nodes; can be 0.
424
  @type exclude: list of nodes or C{None}
425
  @param exclude: nodes to be excluded from the choice
426
  @rtype: list of nodes
427
  @return: C{num} different nodes
428

429
  """
430
  nodes = []
431
  if exclude is None:
432
    exclude = []
433
  elif isinstance(exclude, (list, tuple)):
434
    # Don't modify the incoming argument
435
    exclude = list(exclude)
436
  else:
437
    exclude = [exclude]
438

    
439
  try:
440
    for _ in range(0, num):
441
      n = AcquireNode(exclude=exclude)
442
      nodes.append(n)
443
      exclude.append(n)
444
  except qa_error.OutOfNodesError:
445
    ReleaseManyNodes(nodes)
446
    raise
447
  return nodes
448

    
449

    
450
def ReleaseNode(node):
451
  node["_count"] = node.get("_count", 0) - 1
452

    
453

    
454
def ReleaseManyNodes(nodes):
455
  for n in nodes:
456
    ReleaseNode(n)