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 TSecret(val_type):
446 """Checks if a given value is an instance of Private. 447 448 However, the type is named Secret in the Haskell equivalent. 449 450 """ 451 def fn(val): 452 return isinstance(val, Private) and val_type(val.Get())
453 454 desc = WithDesc("Private %s" % Parens(val_type)) 455 456 return desc(fn) 457
458 459 -def TListOf(my_type):
460 """Checks if a given value is a list with all elements of the same type. 461 462 """ 463 desc = WithDesc("List of %s" % (Parens(my_type), )) 464 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
465 466 467 TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
468 469 470 -def TTupleOf(*val_types):
471 """Checks if a given value is a list with the proper size and its 472 elements match the given types. 473 474 """ 475 desc = WithDesc("Tuple of %s" % Parens(', '.join(str(v) for v in val_types))) 476 return desc(TAnd(TOr(TTuple, TList), TIsLength(len(val_types)), 477 TItems(val_types)))
478
479 480 -def TSetOf(val_type):
481 """Checks if a given value is a list with all elements of the same 482 type and eliminates duplicated elements. 483 484 """ 485 desc = WithDesc("Set of %s" % (Parens(val_type), )) 486 return desc(lambda st: TListOf(val_type)(list(set(st))))
487
488 489 -def TDictOf(key_type, val_type):
490 """Checks a dict type for the type of its key/values. 491 492 """ 493 desc = WithDesc("Dictionary with keys of %s and values of %s" % 494 (Parens(key_type), Parens(val_type))) 495 496 def fn(container): 497 return (compat.all(key_type(v) for v in container.keys()) and 498 compat.all(val_type(v) for v in container.values()))
499 500 return desc(TAnd(TDict, fn)) 501
502 503 -def _TStrictDictCheck(require_all, exclusive, items, val):
504 """Helper function for L{TStrictDict}. 505 506 """ 507 notfound_fn = lambda _: not exclusive 508 509 if require_all and not frozenset(val.keys()).issuperset(items.keys()): 510 # Requires items not found in value 511 return False 512 513 return compat.all(items.get(key, notfound_fn)(value) 514 for (key, value) in val.items())
515
516 517 -def TStrictDict(require_all, exclusive, items):
518 """Strict dictionary check with specific keys. 519 520 @type require_all: boolean 521 @param require_all: Whether all keys in L{items} are required 522 @type exclusive: boolean 523 @param exclusive: Whether only keys listed in L{items} should be accepted 524 @type items: dictionary 525 @param items: Mapping from key (string) to verification function 526 527 """ 528 descparts = ["Dictionary containing"] 529 530 if exclusive: 531 descparts.append(" none but the") 532 533 if require_all: 534 descparts.append(" required") 535 536 if len(items) == 1: 537 descparts.append(" key ") 538 else: 539 descparts.append(" keys ") 540 541 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value) 542 for (key, value) in items.items())) 543 544 desc = WithDesc("".join(descparts)) 545 546 return desc(TAnd(TDict, 547 compat.partial(_TStrictDictCheck, require_all, exclusive, 548 items)))
549
550 551 -def TItems(items):
552 """Checks individual items of a container. 553 554 If the verified value and the list of expected items differ in length, this 555 check considers only as many items as are contained in the shorter list. Use 556 L{TIsLength} to enforce a certain length. 557 558 @type items: list 559 @param items: List of checks 560 561 """ 562 assert items, "Need items" 563 564 text = ["Item", "item"] 565 desc = WithDesc(utils.CommaJoin("%s %s is %s" % 566 (text[int(idx > 0)], idx, Parens(check)) 567 for (idx, check) in enumerate(items))) 568 569 return desc(lambda value: compat.all(check(i) 570 for (check, i) in zip(items, value)))
571 572 573 TMaxValue = lambda max: WithDesc('Less than %s' % max)(lambda val: val < max) 574 TAllocPolicy = TElemOf(constants.VALID_ALLOC_POLICIES) 575 TCVErrorCode = TElemOf(constants.CV_ALL_ECODES_STRINGS) 576 TQueryResultCode = TElemOf(constants.RS_ALL) 577 TExportTarget = TOr(TNonEmptyString, TList) 578 TExportMode = TElemOf(constants.EXPORT_MODES) 579 TDiskIndex = TAnd(TNonNegativeInt, TMaxValue(constants.MAX_DISKS)) 580 TReplaceDisksMode = TElemOf(constants.REPLACE_MODES) 581 TDiskTemplate = TElemOf(constants.DISK_TEMPLATES) 582 TEvacMode = TElemOf(constants.NODE_EVAC_MODES) 583 TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS) 584 TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES) 585 TImportExportCompression = TElemOf(constants.IEC_ALL) 586 TAdminStateSource = TElemOf(constants.ADMIN_STATE_SOURCES)
587 588 589 -def TSetParamsMods(fn):
590 """Generates a check for modification lists. 591 592 """ 593 # Old format 594 # TODO: Remove in version 2.11 including support in LUInstanceSetParams 595 old_mod_item_fn = \ 596 TAnd(TIsLength(2), 597 TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn])) 598 599 # New format, supporting adding/removing disks/NICs at arbitrary indices 600 mod_item_fn = \ 601 TAnd(TIsLength(3), TItems([ 602 TElemOf(constants.DDMS_VALUES_WITH_MODIFY), 603 Comment("Device index, can be negative, e.g. -1 for last disk") 604 (TOr(TInt, TString)), 605 fn, 606 ])) 607 608 return TOr(Comment("Recommended")(TListOf(mod_item_fn)), 609 Comment("Deprecated")(TListOf(old_mod_item_fn)))
610 611 612 TINicParams = \ 613 Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS), 614 TMaybe(TString))) 615 616 TIDiskParams = \ 617 Comment("Disk parameters")(TDictOf(TNonEmptyString, 618 TOr(TNonEmptyString, TInt))) 619 620 THypervisor = TElemOf(constants.HYPER_TYPES) 621 TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES) 622 TNICMode = TElemOf(constants.NIC_VALID_MODES) 623 TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES) 624 TRebootType = TElemOf(constants.REBOOT_TYPES) 625 TFileDriver = TElemOf(constants.FILE_DRIVER) 626 TOobCommand = TElemOf(constants.OOB_COMMANDS) 627 # FIXME: adjust this after all queries are in haskell 628 TQueryTypeOp = TElemOf(set(constants.QR_VIA_OP) 629 .union(set(constants.QR_VIA_LUXI))) 630 631 TDiskParams = \ 632 Comment("Disk parameters")(TDictOf(TNonEmptyString, 633 TOr(TNonEmptyString, TInt))) 634 635 TDiskChanges = \ 636 TAnd(TIsLength(2), 637 TItems([Comment("Disk index")(TNonNegativeInt), 638 Comment("Parameters")(TDiskParams)])) 639 640 TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
641 642 643 -def TStorageType(val):
644 """Builds a function that checks if a given value is a valid storage 645 type. 646 647 """ 648 return (val in constants.STORAGE_TYPES)
649 650 651 TTagKind = TElemOf(constants.VALID_TAG_TYPES) 652 TDdmSimple = TElemOf(constants.DDMS_VALUES) 653 TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS) 654 TSshKeyType = TElemOf(constants.SSHK_ALL)
655 656 657 @WithDesc("IPv4 network") 658 -def _CheckCIDRNetNotation(value):
659 """Ensure a given CIDR notation type is valid. 660 661 """ 662 try: 663 ipaddr.IPv4Network(value) 664 except ipaddr.AddressValueError: 665 return False 666 return True
667
668 669 @WithDesc("IPv4 address") 670 -def _CheckCIDRAddrNotation(value):
671 """Ensure a given CIDR notation type is valid. 672 673 """ 674 try: 675 ipaddr.IPv4Address(value) 676 except ipaddr.AddressValueError: 677 return False 678 return True
679
680 681 @WithDesc("IPv6 address") 682 -def _CheckCIDR6AddrNotation(value):
683 """Ensure a given CIDR notation type is valid. 684 685 """ 686 try: 687 ipaddr.IPv6Address(value) 688 except ipaddr.AddressValueError: 689 return False 690 return True
691
692 693 @WithDesc("IPv6 network") 694 -def _CheckCIDR6NetNotation(value):
695 """Ensure a given CIDR notation type is valid. 696 697 """ 698 try: 699 ipaddr.IPv6Network(value) 700 except ipaddr.AddressValueError: 701 return False 702 return True
703 704 705 TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation) 706 TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation) 707 TIPv4Network = TAnd(TString, _CheckCIDRNetNotation) 708 TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
709 710 711 -def TObject(val_type):
712 return TDictOf(TAny, val_type)
713
714 715 -def TObjectCheck(obj, fields_types):
716 """Helper to generate type checks for objects. 717 718 @param obj: The object to generate type checks 719 @param fields_types: The fields and their types as a dict 720 @return: A ht type check function 721 722 """ 723 assert set(obj.GetAllSlots()) == set(fields_types.keys()), \ 724 "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys())) 725 return TStrictDict(True, True, fields_types)
726 727 728 TQueryFieldDef = \ 729 TObjectCheck(objects.QueryFieldDefinition, { 730 "name": TNonEmptyString, 731 "title": TNonEmptyString, 732 "kind": TElemOf(constants.QFT_ALL), 733 "doc": TNonEmptyString 734 }) 735 736 TQueryRow = \ 737 TListOf(TAnd(TIsLength(2), 738 TItems([TElemOf(constants.RS_ALL), TAny]))) 739 740 TQueryResult = TListOf(TQueryRow) 741 742 TQueryResponse = \ 743 TObjectCheck(objects.QueryResponse, { 744 "fields": TListOf(TQueryFieldDef), 745 "data": TQueryResult 746 }) 747 748 TQueryFieldsResponse = \ 749 TObjectCheck(objects.QueryFieldsResponse, { 750 "fields": TListOf(TQueryFieldDef) 751 }) 752 753 TJobIdListItem = \ 754 TAnd(TIsLength(2), 755 TItems([Comment("success")(TBool), 756 Comment("Job ID if successful, error message" 757 " otherwise")(TOr(TString, TJobId))])) 758 759 TJobIdList = TListOf(TJobIdListItem) 760 761 TJobIdListOnly = TStrictDict(True, True, { 762 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList) 763 }) 764 765 TInstanceMultiAllocResponse = \ 766 TStrictDict(True, True, { 767 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList), 768 constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString), 769 constants.FAILED_KEY: TListOf(TNonEmptyString) 770 }) 771