qa_config.py 6.2 KB
Newer Older
1
2
3
#
#

4
# Copyright (C) 2007, 2011, 2012 Google Inc.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""QA configuration.

"""

26
import os
27

28
from ganeti import constants
29
30
from ganeti import utils
from ganeti import serializer
Iustin Pop's avatar
Iustin Pop committed
31
from ganeti import compat
32
33
34
35

import qa_error


36
_INSTANCE_CHECK_KEY = "instance-check"
37
_ENABLED_HV_KEY = "enabled-hypervisors"
38
39


40
41
42
43
44
45
46
47
cfg = None
options = None


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

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

50
  cfg = serializer.LoadJson(utils.ReadFile(path))
51
52
53
54
55

  Validate()


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

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

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

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

81
82

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


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class Either:
  def __init__(self, tests):
    """Initializes this class.

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

    """
    self.tests = tests


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

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

  """
  if isinstance(value, (list, tuple)):
    return value
  else:
    return [value]


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

  @type check_fn: callable
  @param check_fn: Callback to check whether a test is enabled
  @type names: sequence or string
  @param names: Test name(s)
  @type fn: callable
  @param fn: Aggregation function
  @rtype: bool
  @return: Whether test is enabled

  """
  names = _MakeSequence(names)

  result = []

  for name in names:
    if isinstance(name, Either):
      value = _TestEnabledInner(check_fn, name.tests, compat.any)
    elif isinstance(name, (list, tuple)):
      value = _TestEnabledInner(check_fn, name, compat.all)
    else:
      value = check_fn(name)

    result.append(value)

  return fn(result)


def TestEnabled(tests, _cfg=None):
Iustin Pop's avatar
Iustin Pop committed
142
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
    contain L{Either} for OR conditions, AND is default
146
147

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

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

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

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


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

  """
165
  return cfg.get(_INSTANCE_CHECK_KEY, None) # pylint: disable=E1103
166
167


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def GetEnabledHypervisors():
  """Returns list of enabled hypervisors.

  @rtype: list

  """
  try:
    value = cfg[_ENABLED_HV_KEY]
  except KeyError:
    return [constants.DEFAULT_ENABLED_HYPERVISOR]
  else:
    if isinstance(value, basestring):
      # The configuration key ("enabled-hypervisors") implies there can be
      # multiple values. Multiple hypervisors are comma-separated on the
      # command line option to "gnt-cluster init", so we need to handle them
      # equally here.
      return value.split(",")
    else:
      return value


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

  """
  return GetEnabledHypervisors()[0]


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

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


203
def GetMasterNode():
Iustin Pop's avatar
Iustin Pop committed
204
  return cfg["nodes"][0]
205
206
207
208
209
210
211


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

  """
  # Filter out unwanted instances
Iustin Pop's avatar
Iustin Pop committed
212
213
  tmp_flt = lambda inst: not inst.get("_used", False)
  instances = filter(tmp_flt, cfg["instances"])
214
215
216
217
218
219
  del tmp_flt

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

  inst = instances[0]
Iustin Pop's avatar
Iustin Pop committed
220
  inst["_used"] = True
221
222
223
224
  return inst


def ReleaseInstance(inst):
Iustin Pop's avatar
Iustin Pop committed
225
  inst["_used"] = False
226
227
228
229
230
231
232
233
234
235
236


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

  """
  master = GetMasterNode()

  # Filter out unwanted nodes
  # TODO: Maybe combine filters
  if exclude is None:
Iustin Pop's avatar
Iustin Pop committed
237
    nodes = cfg["nodes"][:]
238
  elif isinstance(exclude, (list, tuple)):
Iustin Pop's avatar
Iustin Pop committed
239
    nodes = filter(lambda node: node not in exclude, cfg["nodes"])
240
  else:
Iustin Pop's avatar
Iustin Pop committed
241
    nodes = filter(lambda node: node != exclude, cfg["nodes"])
242

Iustin Pop's avatar
Iustin Pop committed
243
  tmp_flt = lambda node: node.get("_added", False) or node == master
244
245
246
247
248
249
250
251
  nodes = filter(tmp_flt, nodes)
  del tmp_flt

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

  # Get node with least number of uses
  def compare(a, b):
Iustin Pop's avatar
Iustin Pop committed
252
    result = cmp(a.get("_count", 0), b.get("_count", 0))
253
    if result == 0:
Iustin Pop's avatar
Iustin Pop committed
254
      result = cmp(a["primary"], b["primary"])
255
256
257
258
259
    return result

  nodes.sort(cmp=compare)

  node = nodes[0]
Iustin Pop's avatar
Iustin Pop committed
260
  node["_count"] = node.get("_count", 0) + 1
261
262
263
264
  return node


def ReleaseNode(node):
Iustin Pop's avatar
Iustin Pop committed
265
  node["_count"] = node.get("_count", 0) - 1