Statistics
| Branch: | Tag: | Revision:

root / qa / qa_config.py @ c5cd9637

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

    
44

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

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

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

    
53
  Validate()
54

    
55

    
56
def Validate():
57
  if len(cfg["nodes"]) < 1:
58
    raise qa_error.Error("Need at least one node")
59
  if len(cfg["instances"]) < 1:
60
    raise qa_error.Error("Need at least one instance")
61
  if len(cfg["disk"]) != len(cfg["disk-growth"]):
62
    raise qa_error.Error("Config options 'disk' and 'disk-growth' must have"
63
                         " the same number of items")
64

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

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

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

    
82

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

    
86

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

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

95
    """
96
    self.tests = tests
97

    
98

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

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

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

    
111

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

115
  @type check_fn: callable
116
  @param check_fn: Callback to check whether a test is enabled
117
  @type names: sequence or string
118
  @param names: Test name(s)
119
  @type fn: callable
120
  @param fn: Aggregation function
121
  @rtype: bool
122
  @return: Whether test is enabled
123

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

    
127
  result = []
128

    
129
  for name in names:
130
    if isinstance(name, Either):
131
      value = _TestEnabledInner(check_fn, name.tests, compat.any)
132
    elif isinstance(name, (list, tuple)):
133
      value = _TestEnabledInner(check_fn, name, compat.all)
134
    else:
135
      value = check_fn(name)
136

    
137
    result.append(value)
138

    
139
  return fn(result)
140

    
141

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

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

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

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

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

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

    
161

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

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

    
168

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

172
  @rtype: list
173

174
  """
175
  try:
176
    value = cfg[_ENABLED_HV_KEY]
177
  except KeyError:
178
    return [constants.DEFAULT_ENABLED_HYPERVISOR]
179
  else:
180
    if isinstance(value, basestring):
181
      # The configuration key ("enabled-hypervisors") implies there can be
182
      # multiple values. Multiple hypervisors are comma-separated on the
183
      # command line option to "gnt-cluster init", so we need to handle them
184
      # equally here.
185
      return value.split(",")
186
    else:
187
      return value
188

    
189

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

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

    
196

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

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

    
203

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

    
207

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

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

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

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

    
225

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

    
229

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

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

    
238

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

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

    
245

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

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

    
252

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

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

    
261

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

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

    
271

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

275
  """
276
  master = GetMasterNode()
277

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

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

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

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

    
301
  nodes.sort(cmp=compare)
302

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

    
307

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

311
  @type num: int
312
  @param num: Number of nodes; can be 0.
313
  @type exclude: list of nodes or C{None}
314
  @param exclude: nodes to be excluded from the choice
315
  @rtype: list of nodes
316
  @return: C{num} different nodes
317

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

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

    
338

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

    
342

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