Use node UUID for locking in LUInstanceMove
[ganeti-local] / lib / ht.py
1 #
2 #
3
4 # Copyright (C) 2010, 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 """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 _WrapperBase(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     assert text.strip()
63
64     self._text = text
65     self._fn = fn
66
67   def __call__(self, *args):
68     return self._fn(*args)
69
70
71 class _DescWrapper(_WrapperBase):
72   """Wrapper class for description text.
73
74   """
75   def __str__(self):
76     return self._text
77
78
79 class _CommentWrapper(_WrapperBase):
80   """Wrapper class for comment.
81
82   """
83   def __str__(self):
84     return "%s [%s]" % (self._fn, self._text)
85
86
87 def WithDesc(text):
88   """Builds wrapper class with description text.
89
90   @type text: string
91   @param text: Description text
92   @return: Callable class
93
94   """
95   assert text[0] == text[0].upper()
96
97   return compat.partial(_DescWrapper, text)
98
99
100 def Comment(text):
101   """Builds wrapper for adding comment to description text.
102
103   @type text: string
104   @param text: Comment text
105   @return: Callable class
106
107   """
108   assert not frozenset(text).intersection("[]")
109
110   return compat.partial(_CommentWrapper, text)
111
112
113 def CombinationDesc(op, args, fn):
114   """Build description for combinating operator.
115
116   @type op: string
117   @param op: Operator as text (e.g. "and")
118   @type args: list
119   @param args: Operator arguments
120   @type fn: callable
121   @param fn: Wrapped function
122
123   """
124   # Some type descriptions are rather long. If "None" is listed at the
125   # end or somewhere in between it is easily missed. Therefore it should
126   # be at the beginning, e.g. "None or (long description)".
127   if __debug__ and TNone in args and args.index(TNone) > 0:
128     raise Exception("TNone must be listed first")
129
130   if len(args) == 1:
131     descr = str(args[0])
132   else:
133     descr = (" %s " % op).join(Parens(i) for i in args)
134
135   return WithDesc(descr)(fn)
136
137
138 # Modifiable default values; need to define these here before the
139 # actual LUs
140
141 @WithDesc(str([]))
142 def EmptyList():
143   """Returns an empty list.
144
145   """
146   return []
147
148
149 @WithDesc(str({}))
150 def EmptyDict():
151   """Returns an empty dict.
152
153   """
154   return {}
155
156
157 #: The without-default default value
158 NoDefault = object()
159
160
161 #: The no-type (value too complex to check it in the type system)
162 NoType = object()
163
164
165 # Some basic types
166 @WithDesc("Anything")
167 def TAny(_):
168   """Accepts any value.
169
170   """
171   return True
172
173
174 @WithDesc("NotNone")
175 def TNotNone(val):
176   """Checks if the given value is not None.
177
178   """
179   return val is not None
180
181
182 @WithDesc("None")
183 def TNone(val):
184   """Checks if the given value is None.
185
186   """
187   return val is None
188
189
190 @WithDesc("ValueNone")
191 def TValueNone(val):
192   """Checks if the given value is L{constants.VALUE_NONE}.
193
194   """
195   return val == constants.VALUE_NONE
196
197
198 @WithDesc("Boolean")
199 def TBool(val):
200   """Checks if the given value is a boolean.
201
202   """
203   return isinstance(val, bool)
204
205
206 @WithDesc("Integer")
207 def TInt(val):
208   """Checks if the given value is an integer.
209
210   """
211   # For backwards compatibility with older Python versions, boolean values are
212   # also integers and should be excluded in this test.
213   #
214   # >>> (isinstance(False, int), isinstance(True, int))
215   # (True, True)
216   return isinstance(val, (int, long)) and not isinstance(val, bool)
217
218
219 @WithDesc("Float")
220 def TFloat(val):
221   """Checks if the given value is a float.
222
223   """
224   return isinstance(val, float)
225
226
227 @WithDesc("String")
228 def TString(val):
229   """Checks if the given value is a string.
230
231   """
232   return isinstance(val, basestring)
233
234
235 @WithDesc("EvalToTrue")
236 def TTrue(val):
237   """Checks if a given value evaluates to a boolean True value.
238
239   """
240   return bool(val)
241
242
243 def TElemOf(target_list):
244   """Builds a function that checks if a given value is a member of a list.
245
246   """
247   def fn(val):
248     return val in target_list
249
250   return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
251
252
253 # Container types
254 @WithDesc("List")
255 def TList(val):
256   """Checks if the given value is a list.
257
258   """
259   return isinstance(val, list)
260
261
262 @WithDesc("Tuple")
263 def TTuple(val):
264   """Checks if the given value is a tuple.
265
266   """
267   return isinstance(val, tuple)
268
269
270 @WithDesc("Dictionary")
271 def TDict(val):
272   """Checks if the given value is a dictionary.
273
274   """
275   return isinstance(val, dict)
276
277
278 def TIsLength(size):
279   """Check is the given container is of the given size.
280
281   """
282   def fn(container):
283     return len(container) == size
284
285   return WithDesc("Length %s" % (size, ))(fn)
286
287
288 # Combinator types
289 def TAnd(*args):
290   """Combine multiple functions using an AND operation.
291
292   """
293   def fn(val):
294     return compat.all(t(val) for t in args)
295
296   return CombinationDesc("and", args, fn)
297
298
299 def TOr(*args):
300   """Combine multiple functions using an OR operation.
301
302   """
303   def fn(val):
304     return compat.any(t(val) for t in args)
305
306   return CombinationDesc("or", args, fn)
307
308
309 def TMap(fn, test):
310   """Checks that a modified version of the argument passes the given test.
311
312   """
313   return WithDesc("Result of %s must be %s" %
314                   (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
315
316
317 def TRegex(pobj):
318   """Checks whether a string matches a specific regular expression.
319
320   @param pobj: Compiled regular expression as returned by C{re.compile}
321
322   """
323   desc = WithDesc("String matching regex \"%s\"" %
324                   pobj.pattern.encode("string_escape"))
325
326   return desc(TAnd(TString, pobj.match))
327
328
329 def TMaybe(test):
330   """Wrap a test in a TOr(TNone, test).
331
332   This makes it easier to define TMaybe* types.
333
334   """
335   return TOr(TNone, test)
336
337
338 def TMaybeValueNone(test):
339   """Used for unsetting values.
340
341   """
342   return TMaybe(TOr(TValueNone, test))
343
344
345 # Type aliases
346
347 #: a non-empty string
348 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
349
350 #: a maybe non-empty string
351 TMaybeString = TMaybe(TNonEmptyString)
352
353 #: a maybe boolean (bool or none)
354 TMaybeBool = TMaybe(TBool)
355
356 #: Maybe a dictionary (dict or None)
357 TMaybeDict = TMaybe(TDict)
358
359 #: Maybe a list (list or None)
360 TMaybeList = TMaybe(TList)
361
362 #: a non-negative integer (value >= 0)
363 TNonNegativeInt = \
364   TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0))
365
366 #: a positive integer (value > 0)
367 TPositiveInt = \
368   TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
369
370 #: a maybe positive integer (positive integer or None)
371 TMaybePositiveInt = TMaybe(TPositiveInt)
372
373 #: a negative integer (value < 0)
374 TNegativeInt = \
375   TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
376
377 #: a positive float
378 TNonNegativeFloat = \
379   TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
380
381 #: Job ID
382 TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
383                                TRegex(re.compile("^%s$" %
384                                                  constants.JOB_ID_TEMPLATE))))
385
386 #: Number
387 TNumber = TOr(TInt, TFloat)
388
389 #: Relative job ID
390 TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
391
392
393 def TInstanceOf(cls):
394   """Checks if a given value is an instance of C{cls}.
395
396   @type cls: class
397   @param cls: Class object
398
399   """
400   name = "%s.%s" % (cls.__module__, cls.__name__)
401
402   desc = WithDesc("Instance of %s" % (Parens(name), ))
403
404   return desc(lambda val: isinstance(val, cls))
405
406
407 def TListOf(my_type):
408   """Checks if a given value is a list with all elements of the same type.
409
410   """
411   desc = WithDesc("List of %s" % (Parens(my_type), ))
412   return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
413
414
415 TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
416
417
418 def TDictOf(key_type, val_type):
419   """Checks a dict type for the type of its key/values.
420
421   """
422   desc = WithDesc("Dictionary with keys of %s and values of %s" %
423                   (Parens(key_type), Parens(val_type)))
424
425   def fn(container):
426     return (compat.all(key_type(v) for v in container.keys()) and
427             compat.all(val_type(v) for v in container.values()))
428
429   return desc(TAnd(TDict, fn))
430
431
432 def _TStrictDictCheck(require_all, exclusive, items, val):
433   """Helper function for L{TStrictDict}.
434
435   """
436   notfound_fn = lambda _: not exclusive
437
438   if require_all and not frozenset(val.keys()).issuperset(items.keys()):
439     # Requires items not found in value
440     return False
441
442   return compat.all(items.get(key, notfound_fn)(value)
443                     for (key, value) in val.items())
444
445
446 def TStrictDict(require_all, exclusive, items):
447   """Strict dictionary check with specific keys.
448
449   @type require_all: boolean
450   @param require_all: Whether all keys in L{items} are required
451   @type exclusive: boolean
452   @param exclusive: Whether only keys listed in L{items} should be accepted
453   @type items: dictionary
454   @param items: Mapping from key (string) to verification function
455
456   """
457   descparts = ["Dictionary containing"]
458
459   if exclusive:
460     descparts.append(" none but the")
461
462   if require_all:
463     descparts.append(" required")
464
465   if len(items) == 1:
466     descparts.append(" key ")
467   else:
468     descparts.append(" keys ")
469
470   descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
471                                    for (key, value) in items.items()))
472
473   desc = WithDesc("".join(descparts))
474
475   return desc(TAnd(TDict,
476                    compat.partial(_TStrictDictCheck, require_all, exclusive,
477                                   items)))
478
479
480 def TItems(items):
481   """Checks individual items of a container.
482
483   If the verified value and the list of expected items differ in length, this
484   check considers only as many items as are contained in the shorter list. Use
485   L{TIsLength} to enforce a certain length.
486
487   @type items: list
488   @param items: List of checks
489
490   """
491   assert items, "Need items"
492
493   text = ["Item", "item"]
494   desc = WithDesc(utils.CommaJoin("%s %s is %s" %
495                                   (text[int(idx > 0)], idx, Parens(check))
496                                   for (idx, check) in enumerate(items)))
497
498   return desc(lambda value: compat.all(check(i)
499                                        for (check, i) in zip(items, value)))