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