Statistics
| Branch: | Tag: | Revision:

root / qa / qa_config.py @ e7b6183b

History | View | Annotate | Download (6.1 kB)

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)
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", {})
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)
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