Remove iallocator's “multi-evacuate” mode
[ganeti-local] / lib / ht.py
1 #
2 #
3
4 # Copyright (C) 2010, 2011 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 """Module implementing the parameter types code."""
23
24 import re
25 import operator
26
27 from ganeti import compat
28 from ganeti import utils
29 from ganeti import constants
30
31
32 _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
33
34
35 def Parens(text):
36   """Enclose text in parens if necessary.
37
38   @param text: Text
39
40   """
41   text = str(text)
42
43   if _PAREN_RE.match(text):
44     return text
45   else:
46     return "(%s)" % text
47
48
49 class _DescWrapper(object):
50   __slots__ = [
51     "_fn",
52     "_text",
53     ]
54
55   def __init__(self, text, fn):
56     """Initializes this class.
57
58     @param text: Description
59     @param fn: Wrapped function
60
61     """
62     self._text = text
63     self._fn = fn
64
65   def __call__(self, *args):
66     return self._fn(*args)
67
68   def __str__(self):
69     return self._text
70
71
72 def WithDesc(text):
73   """Builds wrapper class with description text.
74
75   @type text: string
76   @param text: Description text
77   @return: Callable class
78
79   """
80   assert text[0] == text[0].upper()
81
82   return compat.partial(_DescWrapper, text)
83
84
85 def CombinationDesc(op, args, fn):
86   """Build description for combinating operator.
87
88   @type op: string
89   @param op: Operator as text (e.g. "and")
90   @type args: list
91   @param args: Operator arguments
92   @type fn: callable
93   @param fn: Wrapped function
94
95   """
96   if len(args) == 1:
97     descr = str(args[0])
98   else:
99     descr = (" %s " % op).join(Parens(i) for i in args)
100
101   return WithDesc(descr)(fn)
102
103
104 # Modifiable default values; need to define these here before the
105 # actual LUs
106
107 @WithDesc(str([]))
108 def EmptyList():
109   """Returns an empty list.
110
111   """
112   return []
113
114
115 @WithDesc(str({}))
116 def EmptyDict():
117   """Returns an empty dict.
118
119   """
120   return {}
121
122
123 #: The without-default default value
124 NoDefault = object()
125
126
127 #: The no-type (value too complex to check it in the type system)
128 NoType = object()
129
130
131 # Some basic types
132 @WithDesc("Anything")
133 def TAny(_):
134   """Accepts any value.
135
136   """
137   return True
138
139
140 @WithDesc("NotNone")
141 def TNotNone(val):
142   """Checks if the given value is not None.
143
144   """
145   return val is not None
146
147
148 @WithDesc("None")
149 def TNone(val):
150   """Checks if the given value is None.
151
152   """
153   return val is None
154
155
156 @WithDesc("Boolean")
157 def TBool(val):
158   """Checks if the given value is a boolean.
159
160   """
161   return isinstance(val, bool)
162
163
164 @WithDesc("Integer")
165 def TInt(val):
166   """Checks if the given value is an integer.
167
168   """
169   # For backwards compatibility with older Python versions, boolean values are
170   # also integers and should be excluded in this test.
171   #
172   # >>> (isinstance(False, int), isinstance(True, int))
173   # (True, True)
174   return isinstance(val, (int, long)) and not isinstance(val, bool)
175
176
177 @WithDesc("Float")
178 def TFloat(val):
179   """Checks if the given value is a float.
180
181   """
182   return isinstance(val, float)
183
184
185 @WithDesc("String")
186 def TString(val):
187   """Checks if the given value is a string.
188
189   """
190   return isinstance(val, basestring)
191
192
193 @WithDesc("EvalToTrue")
194 def TTrue(val):
195   """Checks if a given value evaluates to a boolean True value.
196
197   """
198   return bool(val)
199
200
201 def TElemOf(target_list):
202   """Builds a function that checks if a given value is a member of a list.
203
204   """
205   def fn(val):
206     return val in target_list
207
208   return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
209
210
211 # Container types
212 @WithDesc("List")
213 def TList(val):
214   """Checks if the given value is a list.
215
216   """
217   return isinstance(val, list)
218
219
220 @WithDesc("Dictionary")
221 def TDict(val):
222   """Checks if the given value is a dictionary.
223
224   """
225   return isinstance(val, dict)
226
227
228 def TIsLength(size):
229   """Check is the given container is of the given size.
230
231   """
232   def fn(container):
233     return len(container) == size
234
235   return WithDesc("Length %s" % (size, ))(fn)
236
237
238 # Combinator types
239 def TAnd(*args):
240   """Combine multiple functions using an AND operation.
241
242   """
243   def fn(val):
244     return compat.all(t(val) for t in args)
245
246   return CombinationDesc("and", args, fn)
247
248
249 def TOr(*args):
250   """Combine multiple functions using an AND operation.
251
252   """
253   def fn(val):
254     return compat.any(t(val) for t in args)
255
256   return CombinationDesc("or", args, fn)
257
258
259 def TMap(fn, test):
260   """Checks that a modified version of the argument passes the given test.
261
262   """
263   return WithDesc("Result of %s must be %s" %
264                   (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
265
266
267 def TRegex(pobj):
268   """Checks whether a string matches a specific regular expression.
269
270   @param pobj: Compiled regular expression as returned by C{re.compile}
271
272   """
273   desc = WithDesc("String matching regex \"%s\"" %
274                   pobj.pattern.encode("string_escape"))
275
276   return desc(TAnd(TString, pobj.match))
277
278
279 # Type aliases
280
281 #: a non-empty string
282 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
283
284 #: a maybe non-empty string
285 TMaybeString = TOr(TNonEmptyString, TNone)
286
287 #: a maybe boolean (bool or none)
288 TMaybeBool = TOr(TBool, TNone)
289
290 #: Maybe a dictionary (dict or None)
291 TMaybeDict = TOr(TDict, TNone)
292
293 #: a positive integer
294 TPositiveInt = \
295   TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
296
297 #: a strictly positive integer
298 TStrictPositiveInt = \
299   TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
300
301 #: a strictly negative integer (0 > value)
302 TStrictNegativeInt = \
303   TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
304
305 #: a positive float
306 TPositiveFloat = \
307   TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
308
309 #: Job ID
310 TJobId = TOr(TPositiveInt,
311              TRegex(re.compile("^%s$" % constants.JOB_ID_TEMPLATE)))
312
313 #: Number
314 TNumber = TOr(TInt, TFloat)
315
316 #: Relative job ID
317 TRelativeJobId = WithDesc("RelativeJobId")(TStrictNegativeInt)
318
319
320 def TListOf(my_type):
321   """Checks if a given value is a list with all elements of the same type.
322
323   """
324   desc = WithDesc("List of %s" % (Parens(my_type), ))
325   return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
326
327
328 def TDictOf(key_type, val_type):
329   """Checks a dict type for the type of its key/values.
330
331   """
332   desc = WithDesc("Dictionary with keys of %s and values of %s" %
333                   (Parens(key_type), Parens(val_type)))
334
335   def fn(container):
336     return (compat.all(key_type(v) for v in container.keys()) and
337             compat.all(val_type(v) for v in container.values()))
338
339   return desc(TAnd(TDict, fn))
340
341
342 def _TStrictDictCheck(require_all, exclusive, items, val):
343   """Helper function for L{TStrictDict}.
344
345   """
346   notfound_fn = lambda _: not exclusive
347
348   if require_all and not frozenset(val.keys()).issuperset(items.keys()):
349     # Requires items not found in value
350     return False
351
352   return compat.all(items.get(key, notfound_fn)(value)
353                     for (key, value) in val.items())
354
355
356 def TStrictDict(require_all, exclusive, items):
357   """Strict dictionary check with specific keys.
358
359   @type require_all: boolean
360   @param require_all: Whether all keys in L{items} are required
361   @type exclusive: boolean
362   @param exclusive: Whether only keys listed in L{items} should be accepted
363   @type items: dictionary
364   @param items: Mapping from key (string) to verification function
365
366   """
367   descparts = ["Dictionary containing"]
368
369   if exclusive:
370     descparts.append(" none but the")
371
372   if require_all:
373     descparts.append(" required")
374
375   if len(items) == 1:
376     descparts.append(" key ")
377   else:
378     descparts.append(" keys ")
379
380   descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
381                                    for (key, value) in items.items()))
382
383   desc = WithDesc("".join(descparts))
384
385   return desc(TAnd(TDict,
386                    compat.partial(_TStrictDictCheck, require_all, exclusive,
387                                   items)))
388
389
390 def TItems(items):
391   """Checks individual items of a container.
392
393   If the verified value and the list of expected items differ in length, this
394   check considers only as many items as are contained in the shorter list. Use
395   L{TIsLength} to enforce a certain length.
396
397   @type items: list
398   @param items: List of checks
399
400   """
401   assert items, "Need items"
402
403   text = ["Item", "item"]
404   desc = WithDesc(utils.CommaJoin("%s %s is %s" %
405                                   (text[int(idx > 0)], idx, Parens(check))
406                                   for (idx, check) in enumerate(items)))
407
408   return desc(lambda value: compat.all(check(i)
409                                        for (check, i) in zip(items, value)))