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  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30   
 31  """Module implementing the parameter types code.""" 
 32   
 33  import re 
 34  import operator 
 35  import ipaddr 
 36   
 37  from ganeti import compat 
 38  from ganeti import utils 
 39  from ganeti import constants 
 40  from ganeti import objects 
 41  from ganeti.serializer import Private 
 42   
 43  _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$") 
44 45 46 -def Parens(text):
47 """Enclose text in parens if necessary. 48 49 @param text: Text 50 51 """ 52 text = str(text) 53 54 if _PAREN_RE.match(text): 55 return text 56 else: 57 return "(%s)" % text
58
59 60 -class _WrapperBase(object):
61 __slots__ = [ 62 "_fn", 63 "_text", 64 ] 65
66 - def __init__(self, text, fn):
67 """Initializes this class. 68 69 @param text: Description 70 @param fn: Wrapped function 71 72 """ 73 assert text.strip() 74 75 self._text = text 76 self._fn = fn
77
78 - def __call__(self, *args):
79 return self._fn(*args)
80
81 82 -class _DescWrapper(_WrapperBase):
83 """Wrapper class for description text. 84 85 """
86 - def __str__(self):
87 return self._text
88
89 - def __repr__(self):
90 return "<%s %r>" % (self._text, self._fn)
91
92 93 -class _CommentWrapper(_WrapperBase):
94 """Wrapper class for comment. 95 96 """
97 - def __str__(self):
98 return "%s [%s]" % (self._fn, self._text)
99
100 101 -def WithDesc(text):
102 """Builds wrapper class with description text. 103 104 @type text: string 105 @param text: Description text 106 @return: Callable class 107 108 """ 109 assert text[0] == text[0].upper() 110 111 return compat.partial(_DescWrapper, text)
112
113 114 -def Comment(text):
115 """Builds wrapper for adding comment to description text. 116 117 @type text: string 118 @param text: Comment text 119 @return: Callable class 120 121 """ 122 assert not frozenset(text).intersection("[]") 123 124 return compat.partial(_CommentWrapper, text)
125
126 127 -def CombinationDesc(op, args, fn):
128 """Build description for combinating operator. 129 130 @type op: string 131 @param op: Operator as text (e.g. "and") 132 @type args: list 133 @param args: Operator arguments 134 @type fn: callable 135 @param fn: Wrapped function 136 137 """ 138 # Some type descriptions are rather long. If "None" is listed at the 139 # end or somewhere in between it is easily missed. Therefore it should 140 # be at the beginning, e.g. "None or (long description)". 141 if __debug__ and TNone in args and args.index(TNone) > 0: 142 raise Exception("TNone must be listed first") 143 144 if len(args) == 1: 145 descr = str(args[0]) 146 else: 147 descr = (" %s " % op).join(Parens(i) for i in args) 148 149 return WithDesc(descr)(fn)
150
151 152 # Modifiable default values; need to define these here before the 153 # actual LUs 154 155 @WithDesc(str([])) 156 -def EmptyList():
157 """Returns an empty list. 158 159 """ 160 return []
161
162 163 @WithDesc(str({})) 164 -def EmptyDict():
165 """Returns an empty dict. 166 167 """ 168 return {}
169 170 171 #: The without-default default value 172 NoDefault = object()
173 174 175 # Some basic types 176 @WithDesc("Anything") 177 -def TAny(_):
178 """Accepts any value. 179 180 """ 181 return True
182
183 184 @WithDesc("NotNone") 185 -def TNotNone(val):
186 """Checks if the given value is not None. 187 188 """ 189 return val is not None
190
191 192 @WithDesc("None") 193 -def TNone(val):
194 """Checks if the given value is None. 195 196 """ 197 return val is None
198
199 200 @WithDesc("ValueNone") 201 -def TValueNone(val):
202 """Checks if the given value is L{constants.VALUE_NONE}. 203 204 """ 205 return val == constants.VALUE_NONE
206
207 208 @WithDesc("Boolean") 209 -def TBool(val):
210 """Checks if the given value is a boolean. 211 212 """ 213 return isinstance(val, bool)
214
215 216 @WithDesc("Integer") 217 -def TInt(val):
218 """Checks if the given value is an integer. 219 220 """ 221 # For backwards compatibility with older Python versions, boolean values are 222 # also integers and should be excluded in this test. 223 # 224 # >>> (isinstance(False, int), isinstance(True, int)) 225 # (True, True) 226 return isinstance(val, (int, long)) and not isinstance(val, bool)
227
228 229 @WithDesc("Float") 230 -def TFloat(val):
231 """Checks if the given value is a float. 232 233 """ 234 return isinstance(val, float)
235
236 237 @WithDesc("String") 238 -def TString(val):
239 """Checks if the given value is a string. 240 241 """ 242 return isinstance(val, basestring)
243
244 245 @WithDesc("EvalToTrue") 246 -def TTrue(val):
247 """Checks if a given value evaluates to a boolean True value. 248 249 """ 250 return bool(val)
251
252 253 -def TElemOf(target_list):
254 """Builds a function that checks if a given value is a member of a list. 255 256 """ 257 def fn(val): 258 return val in target_list
259 260 return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn) 261
262 263 # Container types 264 @WithDesc("List") 265 -def TList(val):
266 """Checks if the given value is a list. 267 268 """ 269 return isinstance(val, list)
270
271 272 @WithDesc("Tuple") 273 -def TTuple(val):
274 """Checks if the given value is a tuple. 275 276 """ 277 return isinstance(val, tuple)
278
279 280 @WithDesc("Dictionary") 281 -def TDict(val):
282 """Checks if the given value is a dictionary. 283 284 Note that L{PrivateDict}s subclass dict and pass this check. 285 286 """ 287 return isinstance(val, dict)
288
289 290 -def TIsLength(size):
291 """Check is the given container is of the given size. 292 293 """ 294 def fn(container): 295 return len(container) == size
296 297 return WithDesc("Length %s" % (size, ))(fn) 298
299 300 # Combinator types 301 -def TAnd(*args):
302 """Combine multiple functions using an AND operation. 303 304 """ 305 def fn(val): 306 return compat.all(t(val) for t in args)
307 308 return CombinationDesc("and", args, fn) 309
310 311 -def TOr(*args):
312 """Combine multiple functions using an OR operation. 313 314 """ 315 def fn(val): 316 return compat.any(t(val) for t in args)
317 318 return CombinationDesc("or", args, fn) 319
320 321 -def TMap(fn, test):
322 """Checks that a modified version of the argument passes the given test. 323 324 """ 325 return WithDesc("Result of %s must be %s" % 326 (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
327
328 329 -def TRegex(pobj):
330 """Checks whether a string matches a specific regular expression. 331 332 @param pobj: Compiled regular expression as returned by C{re.compile} 333 334 """ 335 desc = WithDesc("String matching regex \"%s\"" % 336 pobj.pattern.encode("string_escape")) 337 338 return desc(TAnd(TString, pobj.match))
339
340 341 -def TMaybe(test):
342 """Wrap a test in a TOr(TNone, test). 343 344 This makes it easier to define TMaybe* types. 345 346 """ 347 return TOr(TNone, test)
348
349 350 -def TMaybeValueNone(test):
351 """Used for unsetting values. 352 353 """ 354 return TMaybe(TOr(TValueNone, test))
355 356 357 # Type aliases 358 359 #: a non-empty string 360 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue)) 361 362 #: a maybe non-empty string 363 TMaybeString = TMaybe(TNonEmptyString) 364 365 #: a maybe boolean (bool or none) 366 TMaybeBool = TMaybe(TBool) 367 368 #: Maybe a dictionary (dict or None) 369 TMaybeDict = TMaybe(TDict) 370 371 #: Maybe a list (list or None) 372 TMaybeList = TMaybe(TList)
373 374 375 #: a non-negative number (value > 0) 376 # val_type should be TInt, TDouble (== TFloat), or TNumber 377 -def TNonNegative(val_type):
378 return WithDesc("EqualOrGreaterThanZero")(TAnd(val_type, lambda v: v >= 0))
379
380 381 #: a positive number (value >= 0) 382 # val_type should be TInt, TDouble (== TFloat), or TNumber 383 -def TPositive(val_type):
384 return WithDesc("GreaterThanZero")(TAnd(val_type, lambda v: v > 0))
385 386 387 #: a non-negative integer (value >= 0) 388 TNonNegativeInt = TNonNegative(TInt) 389 390 #: a positive integer (value > 0) 391 TPositiveInt = TPositive(TInt) 392 393 #: a maybe positive integer (positive integer or None) 394 TMaybePositiveInt = TMaybe(TPositiveInt) 395 396 #: a negative integer (value < 0) 397 TNegativeInt = \ 398 TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0))) 399 400 #: a positive float 401 TNonNegativeFloat = \ 402 TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0)) 403 404 #: Job ID 405 TJobId = WithDesc("JobId")(TOr(TNonNegativeInt, 406 TRegex(re.compile("^%s$" % 407 constants.JOB_ID_TEMPLATE)))) 408 409 #: Double (== Float) 410 TDouble = TFloat 411 412 #: Number 413 TNumber = TOr(TInt, TFloat) 414 415 #: Relative job ID 416 TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
417 418 419 -def TInstanceOf(cls):
420 """Checks if a given value is an instance of C{cls}. 421 422 @type cls: class 423 @param cls: Class object 424 425 """ 426 name = "%s.%s" % (cls.__module__, cls.__name__) 427 428 desc = WithDesc("Instance of %s" % (Parens(name), )) 429 430 return desc(lambda val: isinstance(val, cls))
431
432 433 -def TPrivate(val_type):
434 """Checks if a given value is an instance of Private. 435 436 """ 437 def fn(val): 438 return isinstance(val, Private) and val_type(val.Get())
439 440 desc = WithDesc("Private %s" % Parens(val_type)) 441 442 return desc(fn) 443
444 445 -def TListOf(my_type):
446 """Checks if a given value is a list with all elements of the same type. 447 448 """ 449 desc = WithDesc("List of %s" % (Parens(my_type), )) 450 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
451 452 453 TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
454 455 456 -def TTupleOf(*val_types):
457 """Checks if a given value is a list with the proper size and its 458 elements match the given types. 459 460 """ 461 desc = WithDesc("Tuple of %s" % Parens(', '.join(str(v) for v in val_types))) 462 return desc(TAnd(TOr(TTuple, TList), TIsLength(len(val_types)), 463 TItems(val_types)))
464
465 466 -def TSetOf(val_type):
467 """Checks if a given value is a list with all elements of the same 468 type and eliminates duplicated elements. 469 470 """ 471 desc = WithDesc("Set of %s" % (Parens(val_type), )) 472 return desc(lambda st: TListOf(val_type)(list(set(st))))
473
474 475 -def TDictOf(key_type, val_type):
476 """Checks a dict type for the type of its key/values. 477 478 """ 479 desc = WithDesc("Dictionary with keys of %s and values of %s" % 480 (Parens(key_type), Parens(val_type))) 481 482 def fn(container): 483 return (compat.all(key_type(v) for v in container.keys()) and 484 compat.all(val_type(v) for v in container.values()))
485 486 return desc(TAnd(TDict, fn)) 487
488 489 -def _TStrictDictCheck(require_all, exclusive, items, val):
490 """Helper function for L{TStrictDict}. 491 492 """ 493 notfound_fn = lambda _: not exclusive 494 495 if require_all and not frozenset(val.keys()).issuperset(items.keys()): 496 # Requires items not found in value 497 return False 498 499 return compat.all(items.get(key, notfound_fn)(value) 500 for (key, value) in val.items())
501
502 503 -def TStrictDict(require_all, exclusive, items):
504 """Strict dictionary check with specific keys. 505 506 @type require_all: boolean 507 @param require_all: Whether all keys in L{items} are required 508 @type exclusive: boolean 509 @param exclusive: Whether only keys listed in L{items} should be accepted 510 @type items: dictionary 511 @param items: Mapping from key (string) to verification function 512 513 """ 514 descparts = ["Dictionary containing"] 515 516 if exclusive: 517 descparts.append(" none but the") 518 519 if require_all: 520 descparts.append(" required") 521 522 if len(items) == 1: 523 descparts.append(" key ") 524 else: 525 descparts.append(" keys ") 526 527 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value) 528 for (key, value) in items.items())) 529 530 desc = WithDesc("".join(descparts)) 531 532 return desc(TAnd(TDict, 533 compat.partial(_TStrictDictCheck, require_all, exclusive, 534 items)))
535
536 537 -def TItems(items):
538 """Checks individual items of a container. 539 540 If the verified value and the list of expected items differ in length, this 541 check considers only as many items as are contained in the shorter list. Use 542 L{TIsLength} to enforce a certain length. 543 544 @type items: list 545 @param items: List of checks 546 547 """ 548 assert items, "Need items" 549 550 text = ["Item", "item"] 551 desc = WithDesc(utils.CommaJoin("%s %s is %s" % 552 (text[int(idx > 0)], idx, Parens(check)) 553 for (idx, check) in enumerate(items))) 554 555 return desc(lambda value: compat.all(check(i) 556 for (check, i) in zip(items, value)))
557 558 559 TMaxValue = lambda max: WithDesc('Less than %s' % max)(lambda val: val < max) 560 TAllocPolicy = TElemOf(constants.VALID_ALLOC_POLICIES) 561 TCVErrorCode = TElemOf(constants.CV_ALL_ECODES_STRINGS) 562 TQueryResultCode = TElemOf(constants.RS_ALL) 563 TExportTarget = TOr(TNonEmptyString, TList) 564 TExportMode = TElemOf(constants.EXPORT_MODES) 565 TDiskIndex = TAnd(TNonNegativeInt, TMaxValue(constants.MAX_DISKS)) 566 TReplaceDisksMode = TElemOf(constants.REPLACE_MODES) 567 TDiskTemplate = TElemOf(constants.DISK_TEMPLATES) 568 TEvacMode = TElemOf(constants.NODE_EVAC_MODES) 569 TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS) 570 TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES) 571 TImportExportCompression = TElemOf(constants.IEC_ALL) 572 TAdminStateSource = TElemOf(constants.ADMIN_STATE_SOURCES)
573 574 575 -def TSetParamsMods(fn):
576 """Generates a check for modification lists. 577 578 """ 579 # Old format 580 # TODO: Remove in version 2.11 including support in LUInstanceSetParams 581 old_mod_item_fn = \ 582 TAnd(TIsLength(2), 583 TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn])) 584 585 # New format, supporting adding/removing disks/NICs at arbitrary indices 586 mod_item_fn = \ 587 TAnd(TIsLength(3), TItems([ 588 TElemOf(constants.DDMS_VALUES_WITH_MODIFY), 589 Comment("Device index, can be negative, e.g. -1 for last disk") 590 (TOr(TInt, TString)), 591 fn, 592 ])) 593 594 return TOr(Comment("Recommended")(TListOf(mod_item_fn)), 595 Comment("Deprecated")(TListOf(old_mod_item_fn)))
596 597 598 TINicParams = \ 599 Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS), 600 TMaybe(TString))) 601 602 TIDiskParams = \ 603 Comment("Disk parameters")(TDictOf(TNonEmptyString, 604 TOr(TNonEmptyString, TInt))) 605 606 THypervisor = TElemOf(constants.HYPER_TYPES) 607 TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES) 608 TNICMode = TElemOf(constants.NIC_VALID_MODES) 609 TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES) 610 TRebootType = TElemOf(constants.REBOOT_TYPES) 611 TFileDriver = TElemOf(constants.FILE_DRIVER) 612 TOobCommand = TElemOf(constants.OOB_COMMANDS) 613 # FIXME: adjust this after all queries are in haskell 614 TQueryTypeOp = TElemOf(set(constants.QR_VIA_OP) 615 .union(set(constants.QR_VIA_LUXI))) 616 617 TDiskParams = \ 618 Comment("Disk parameters")(TDictOf(TNonEmptyString, 619 TOr(TNonEmptyString, TInt))) 620 621 TDiskChanges = \ 622 TAnd(TIsLength(2), 623 TItems([Comment("Disk index")(TNonNegativeInt), 624 Comment("Parameters")(TDiskParams)])) 625 626 TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
627 628 629 -def TStorageType(val):
630 """Builds a function that checks if a given value is a valid storage 631 type. 632 633 """ 634 return (val in constants.STORAGE_TYPES)
635 636 637 TTagKind = TElemOf(constants.VALID_TAG_TYPES) 638 TDdmSimple = TElemOf(constants.DDMS_VALUES) 639 TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
640 641 642 @WithDesc("IPv4 network") 643 -def _CheckCIDRNetNotation(value):
644 """Ensure a given CIDR notation type is valid. 645 646 """ 647 try: 648 ipaddr.IPv4Network(value) 649 except ipaddr.AddressValueError: 650 return False 651 return True
652
653 654 @WithDesc("IPv4 address") 655 -def _CheckCIDRAddrNotation(value):
656 """Ensure a given CIDR notation type is valid. 657 658 """ 659 try: 660 ipaddr.IPv4Address(value) 661 except ipaddr.AddressValueError: 662 return False 663 return True
664
665 666 @WithDesc("IPv6 address") 667 -def _CheckCIDR6AddrNotation(value):
668 """Ensure a given CIDR notation type is valid. 669 670 """ 671 try: 672 ipaddr.IPv6Address(value) 673 except ipaddr.AddressValueError: 674 return False 675 return True
676
677 678 @WithDesc("IPv6 network") 679 -def _CheckCIDR6NetNotation(value):
680 """Ensure a given CIDR notation type is valid. 681 682 """ 683 try: 684 ipaddr.IPv6Network(value) 685 except ipaddr.AddressValueError: 686 return False 687 return True
688 689 690 TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation) 691 TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation) 692 TIPv4Network = TAnd(TString, _CheckCIDRNetNotation) 693 TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
694 695 696 -def TObject(val_type):
697 return TDictOf(TAny, val_type)
698
699 700 -def TObjectCheck(obj, fields_types):
701 """Helper to generate type checks for objects. 702 703 @param obj: The object to generate type checks 704 @param fields_types: The fields and their types as a dict 705 @return: A ht type check function 706 707 """ 708 assert set(obj.GetAllSlots()) == set(fields_types.keys()), \ 709 "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys())) 710 return TStrictDict(True, True, fields_types)
711 712 713 TQueryFieldDef = \ 714 TObjectCheck(objects.QueryFieldDefinition, { 715 "name": TNonEmptyString, 716 "title": TNonEmptyString, 717 "kind": TElemOf(constants.QFT_ALL), 718 "doc": TNonEmptyString 719 }) 720 721 TQueryRow = \ 722 TListOf(TAnd(TIsLength(2), 723 TItems([TElemOf(constants.RS_ALL), TAny]))) 724 725 TQueryResult = TListOf(TQueryRow) 726 727 TQueryResponse = \ 728 TObjectCheck(objects.QueryResponse, { 729 "fields": TListOf(TQueryFieldDef), 730 "data": TQueryResult 731 }) 732 733 TQueryFieldsResponse = \ 734 TObjectCheck(objects.QueryFieldsResponse, { 735 "fields": TListOf(TQueryFieldDef) 736 }) 737 738 TJobIdListItem = \ 739 TAnd(TIsLength(2), 740 TItems([Comment("success")(TBool), 741 Comment("Job ID if successful, error message" 742 " otherwise")(TOr(TString, TJobId))])) 743 744 TJobIdList = TListOf(TJobIdListItem) 745 746 TJobIdListOnly = TStrictDict(True, True, { 747 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList) 748 }) 749 750 TInstanceMultiAllocResponse = \ 751 TStrictDict(True, True, { 752 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList), 753 constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString), 754 constants.FAILED_KEY: TListOf(TNonEmptyString) 755 }) 756