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