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

Iustin Pop's avatar
Iustin Pop committed
4
# Copyright (C) 2007, 2011 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
29
from ganeti import utils
from ganeti import serializer
Iustin Pop's avatar
Iustin Pop committed
30
from ganeti import compat
31
32
33
34

import qa_error


35
36
37
_INSTANCE_CHECK_KEY = "instance-check"


38
39
40
41
42
43
44
45
cfg = None
options = None


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

  """
46
  global cfg # pylint: disable=W0603
47

48
  cfg = serializer.LoadJson(utils.ReadFile(path))
49
50
51
52
53

  Validate()


def Validate():
Iustin Pop's avatar
Iustin Pop committed
54
  if len(cfg["nodes"]) < 1:
55
    raise qa_error.Error("Need at least one node")
Iustin Pop's avatar
Iustin Pop committed
56
  if len(cfg["instances"]) < 1:
57
    raise qa_error.Error("Need at least one instance")
58
59
60
  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")
61

62
63
64
65
66
67
68
69
  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))

70
71
72
73
74

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


75
76
77
78
79
80
81
82
83
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
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
131
132
  """Returns True if the given tests are enabled.

133
134
  @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
135
136

  """
137
138
  if _cfg is None:
    _cfg = cfg
139
140

  # Get settings for all tests
141
  cfg_tests = _cfg.get("tests", {})
142
143

  # Get default setting
144
  default = cfg_tests.get("default", True)
145

146
147
  return _TestEnabledInner(lambda name: cfg_tests.get(name, default),
                           tests, compat.all)
148
149


150
151
152
153
154
155
156
def GetInstanceCheckScript():
  """Returns path to instance check script or C{None}.

  """
  return cfg.get(_INSTANCE_CHECK_KEY, None)


157
def GetMasterNode():
Iustin Pop's avatar
Iustin Pop committed
158
  return cfg["nodes"][0]
159
160
161
162
163
164
165


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

  """
  # Filter out unwanted instances
Iustin Pop's avatar
Iustin Pop committed
166
167
  tmp_flt = lambda inst: not inst.get("_used", False)
  instances = filter(tmp_flt, cfg["instances"])
168
169
170
171
172
173
  del tmp_flt

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

  inst = instances[0]
Iustin Pop's avatar
Iustin Pop committed
174
  inst["_used"] = True
175
176
177
178
  return inst


def ReleaseInstance(inst):
Iustin Pop's avatar
Iustin Pop committed
179
  inst["_used"] = False
180
181
182
183
184
185
186
187
188
189
190


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
191
    nodes = cfg["nodes"][:]
192
  elif isinstance(exclude, (list, tuple)):
Iustin Pop's avatar
Iustin Pop committed
193
    nodes = filter(lambda node: node not in exclude, cfg["nodes"])
194
  else:
Iustin Pop's avatar
Iustin Pop committed
195
    nodes = filter(lambda node: node != exclude, cfg["nodes"])
196

Iustin Pop's avatar
Iustin Pop committed
197
  tmp_flt = lambda node: node.get("_added", False) or node == master
198
199
200
201
202
203
204
205
  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
206
    result = cmp(a.get("_count", 0), b.get("_count", 0))
207
    if result == 0:
Iustin Pop's avatar
Iustin Pop committed
208
      result = cmp(a["primary"], b["primary"])
209
210
211
212
213
    return result

  nodes.sort(cmp=compare)

  node = nodes[0]
Iustin Pop's avatar
Iustin Pop committed
214
  node["_count"] = node.get("_count", 0) + 1
215
216
217
218
  return node


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