Package ganeti :: Module ht
[hide private]
[frames] | no frames]

Source Code for Module ganeti.ht

  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 AND 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 #: a non-negative integer (value >= 0) 360 TNonNegativeInt = \ 361 TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0)) 362 363 #: a positive integer (value > 0) 364 TPositiveInt = \ 365 TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0)) 366 367 #: a maybe positive integer (positive integer or None) 368 TMaybePositiveInt = TMaybe(TPositiveInt) 369 370 #: a negative integer (value < 0) 371 TNegativeInt = \ 372 TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0))) 373 374 #: a positive float 375 TNonNegativeFloat = \ 376 TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0)) 377 378 #: Job ID 379 TJobId = WithDesc("JobId")(TOr(TNonNegativeInt, 380 TRegex(re.compile("^%s$" % 381 constants.JOB_ID_TEMPLATE)))) 382 383 #: Number 384 TNumber = TOr(TInt, TFloat) 385 386 #: Relative job ID 387 TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
388 389 390 -def TInstanceOf(cls):
391 """Checks if a given value is an instance of C{cls}. 392 393 @type cls: class 394 @param cls: Class object 395 396 """ 397 name = "%s.%s" % (cls.__module__, cls.__name__) 398 399 desc = WithDesc("Instance of %s" % (Parens(name), )) 400 401 return desc(lambda val: isinstance(val, cls))
402
403 404 -def TListOf(my_type):
405 """Checks if a given value is a list with all elements of the same type. 406 407 """ 408 desc = WithDesc("List of %s" % (Parens(my_type), )) 409 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
410 411 412 TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
413 414 415 -def TDictOf(key_type, val_type):
416 """Checks a dict type for the type of its key/values. 417 418 """ 419 desc = WithDesc("Dictionary with keys of %s and values of %s" % 420 (Parens(key_type), Parens(val_type))) 421 422 def fn(container): 423 return (compat.all(key_type(v) for v in container.keys()) and 424 compat.all(val_type(v) for v in container.values()))
425 426 return desc(TAnd(TDict, fn)) 427
428 429 -def _TStrictDictCheck(require_all, exclusive, items, val):
430 """Helper function for L{TStrictDict}. 431 432 """ 433 notfound_fn = lambda _: not exclusive 434 435 if require_all and not frozenset(val.keys()).issuperset(items.keys()): 436 # Requires items not found in value 437 return False 438 439 return compat.all(items.get(key, notfound_fn)(value) 440 for (key, value) in val.items())
441
442 443 -def TStrictDict(require_all, exclusive, items):
444 """Strict dictionary check with specific keys. 445 446 @type require_all: boolean 447 @param require_all: Whether all keys in L{items} are required 448 @type exclusive: boolean 449 @param exclusive: Whether only keys listed in L{items} should be accepted 450 @type items: dictionary 451 @param items: Mapping from key (string) to verification function 452 453 """ 454 descparts = ["Dictionary containing"] 455 456 if exclusive: 457 descparts.append(" none but the") 458 459 if require_all: 460 descparts.append(" required") 461 462 if len(items) == 1: 463 descparts.append(" key ") 464 else: 465 descparts.append(" keys ") 466 467 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value) 468 for (key, value) in items.items())) 469 470 desc = WithDesc("".join(descparts)) 471 472 return desc(TAnd(TDict, 473 compat.partial(_TStrictDictCheck, require_all, exclusive, 474 items)))
475
476 477 -def TItems(items):
478 """Checks individual items of a container. 479 480 If the verified value and the list of expected items differ in length, this 481 check considers only as many items as are contained in the shorter list. Use 482 L{TIsLength} to enforce a certain length. 483 484 @type items: list 485 @param items: List of checks 486 487 """ 488 assert items, "Need items" 489 490 text = ["Item", "item"] 491 desc = WithDesc(utils.CommaJoin("%s %s is %s" % 492 (text[int(idx > 0)], idx, Parens(check)) 493 for (idx, check) in enumerate(items))) 494 495 return desc(lambda value: compat.all(check(i) 496 for (check, i) in zip(items, value)))
497