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