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(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 TAllocPolicy = TElemOf(constants.VALID_ALLOC_POLICIES) 560 TCVErrorCode = TElemOf(constants.CV_ALL_ECODES_STRINGS) 561 TQueryResultCode = TElemOf(constants.RS_ALL) 562 TExportTarget = TOr(TNonEmptyString, TList) 563 TExportMode = TElemOf(constants.EXPORT_MODES) 564 TDiskIndex = TAnd(TNonNegativeInt, lambda val: val < constants.MAX_DISKS) 565 TReplaceDisksMode = TElemOf(constants.REPLACE_MODES) 566 TDiskTemplate = TElemOf(constants.DISK_TEMPLATES) 567 TEvacMode = TElemOf(constants.NODE_EVAC_MODES) 568 TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS) 569 TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES) 570 TImportExportCompression = TElemOf(constants.IEC_ALL) 571 TAdminStateSource = TElemOf(constants.ADMIN_STATE_SOURCES)
572 573 574 -def TSetParamsMods(fn):
575 """Generates a check for modification lists. 576 577 """ 578 # Old format 579 # TODO: Remove in version 2.11 including support in LUInstanceSetParams 580 old_mod_item_fn = \ 581 TAnd(TIsLength(2), 582 TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn])) 583 584 # New format, supporting adding/removing disks/NICs at arbitrary indices 585 mod_item_fn = \ 586 TAnd(TIsLength(3), TItems([ 587 TElemOf(constants.DDMS_VALUES_WITH_MODIFY), 588 Comment("Device index, can be negative, e.g. -1 for last disk") 589 (TOr(TInt, TString)), 590 fn, 591 ])) 592 593 return TOr(Comment("Recommended")(TListOf(mod_item_fn)), 594 Comment("Deprecated")(TListOf(old_mod_item_fn)))
595 596 597 TINicParams = \ 598 Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS), 599 TMaybe(TString))) 600 601 TIDiskParams = \ 602 Comment("Disk parameters")(TDictOf(TNonEmptyString, 603 TOr(TNonEmptyString, TInt))) 604 605 THypervisor = TElemOf(constants.HYPER_TYPES) 606 TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES) 607 TNICMode = TElemOf(constants.NIC_VALID_MODES) 608 TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES) 609 TRebootType = TElemOf(constants.REBOOT_TYPES) 610 TFileDriver = TElemOf(constants.FILE_DRIVER) 611 TOobCommand = TElemOf(constants.OOB_COMMANDS) 612 # FIXME: adjust this after all queries are in haskell 613 TQueryTypeOp = TElemOf(set(constants.QR_VIA_OP) 614 .union(set(constants.QR_VIA_LUXI))) 615 616 TDiskParams = \ 617 Comment("Disk parameters")(TDictOf(TNonEmptyString, 618 TOr(TNonEmptyString, TInt))) 619 620 TDiskChanges = \ 621 TAnd(TIsLength(2), 622 TItems([Comment("Disk index")(TNonNegativeInt), 623 Comment("Parameters")(TDiskParams)])) 624 625 TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
626 627 628 -def TStorageType(val):
629 """Builds a function that checks if a given value is a valid storage 630 type. 631 632 """ 633 return (val in constants.STORAGE_TYPES)
634 635 636 TTagKind = TElemOf(constants.VALID_TAG_TYPES) 637 TDdmSimple = TElemOf(constants.DDMS_VALUES) 638 TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
639 640 641 @WithDesc("IPv4 network") 642 -def _CheckCIDRNetNotation(value):
643 """Ensure a given CIDR notation type is valid. 644 645 """ 646 try: 647 ipaddr.IPv4Network(value) 648 except ipaddr.AddressValueError: 649 return False 650 return True
651
652 653 @WithDesc("IPv4 address") 654 -def _CheckCIDRAddrNotation(value):
655 """Ensure a given CIDR notation type is valid. 656 657 """ 658 try: 659 ipaddr.IPv4Address(value) 660 except ipaddr.AddressValueError: 661 return False 662 return True
663
664 665 @WithDesc("IPv6 address") 666 -def _CheckCIDR6AddrNotation(value):
667 """Ensure a given CIDR notation type is valid. 668 669 """ 670 try: 671 ipaddr.IPv6Address(value) 672 except ipaddr.AddressValueError: 673 return False 674 return True
675
676 677 @WithDesc("IPv6 network") 678 -def _CheckCIDR6NetNotation(value):
679 """Ensure a given CIDR notation type is valid. 680 681 """ 682 try: 683 ipaddr.IPv6Network(value) 684 except ipaddr.AddressValueError: 685 return False 686 return True
687 688 689 TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation) 690 TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation) 691 TIPv4Network = TAnd(TString, _CheckCIDRNetNotation) 692 TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
693 694 695 -def TObject(val_type):
696 return TDictOf(TAny, val_type)
697
698 699 -def TObjectCheck(obj, fields_types):
700 """Helper to generate type checks for objects. 701 702 @param obj: The object to generate type checks 703 @param fields_types: The fields and their types as a dict 704 @return: A ht type check function 705 706 """ 707 assert set(obj.GetAllSlots()) == set(fields_types.keys()), \ 708 "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys())) 709 return TStrictDict(True, True, fields_types)
710 711 712 TQueryFieldDef = \ 713 TObjectCheck(objects.QueryFieldDefinition, { 714 "name": TNonEmptyString, 715 "title": TNonEmptyString, 716 "kind": TElemOf(constants.QFT_ALL), 717 "doc": TNonEmptyString 718 }) 719 720 TQueryRow = \ 721 TListOf(TAnd(TIsLength(2), 722 TItems([TElemOf(constants.RS_ALL), TAny]))) 723 724 TQueryResult = TListOf(TQueryRow) 725 726 TQueryResponse = \ 727 TObjectCheck(objects.QueryResponse, { 728 "fields": TListOf(TQueryFieldDef), 729 "data": TQueryResult 730 }) 731 732 TQueryFieldsResponse = \ 733 TObjectCheck(objects.QueryFieldsResponse, { 734 "fields": TListOf(TQueryFieldDef) 735 }) 736 737 TJobIdListItem = \ 738 TAnd(TIsLength(2), 739 TItems([Comment("success")(TBool), 740 Comment("Job ID if successful, error message" 741 " otherwise")(TOr(TString, TJobId))])) 742 743 TJobIdList = TListOf(TJobIdListItem) 744 745 TJobIdListOnly = TStrictDict(True, True, { 746 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList) 747 }) 748 749 TInstanceMultiAllocResponse = \ 750 TStrictDict(True, True, { 751 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList), 752 constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString), 753 constants.FAILED_KEY: TListOf(TNonEmptyString) 754 }) 755