4 # Copyright (C) 2010, 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Module implementing the parameter types code."""
28 from ganeti import compat
29 from ganeti import utils
30 from ganeti import constants
31 from ganeti import objects
34 _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
38 """Enclose text in parens if necessary.
45 if _PAREN_RE.match(text):
51 class _WrapperBase(object):
57 def __init__(self, text, fn):
58 """Initializes this class.
60 @param text: Description
61 @param fn: Wrapped function
69 def __call__(self, *args):
70 return self._fn(*args)
73 class _DescWrapper(_WrapperBase):
74 """Wrapper class for description text.
81 class _CommentWrapper(_WrapperBase):
82 """Wrapper class for comment.
86 return "%s [%s]" % (self._fn, self._text)
90 """Builds wrapper class with description text.
93 @param text: Description text
94 @return: Callable class
97 assert text[0] == text[0].upper()
99 return compat.partial(_DescWrapper, text)
103 """Builds wrapper for adding comment to description text.
106 @param text: Comment text
107 @return: Callable class
110 assert not frozenset(text).intersection("[]")
112 return compat.partial(_CommentWrapper, text)
115 def CombinationDesc(op, args, fn):
116 """Build description for combinating operator.
119 @param op: Operator as text (e.g. "and")
121 @param args: Operator arguments
123 @param fn: Wrapped function
126 # Some type descriptions are rather long. If "None" is listed at the
127 # end or somewhere in between it is easily missed. Therefore it should
128 # be at the beginning, e.g. "None or (long description)".
129 if __debug__ and TNone in args and args.index(TNone) > 0:
130 raise Exception("TNone must be listed first")
135 descr = (" %s " % op).join(Parens(i) for i in args)
137 return WithDesc(descr)(fn)
140 # Modifiable default values; need to define these here before the
145 """Returns an empty list.
153 """Returns an empty dict.
159 #: The without-default default value
164 @WithDesc("Anything")
166 """Accepts any value.
174 """Checks if the given value is not None.
177 return val is not None
182 """Checks if the given value is None.
188 @WithDesc("ValueNone")
190 """Checks if the given value is L{constants.VALUE_NONE}.
193 return val == constants.VALUE_NONE
198 """Checks if the given value is a boolean.
201 return isinstance(val, bool)
206 """Checks if the given value is an integer.
209 # For backwards compatibility with older Python versions, boolean values are
210 # also integers and should be excluded in this test.
212 # >>> (isinstance(False, int), isinstance(True, int))
214 return isinstance(val, (int, long)) and not isinstance(val, bool)
219 """Checks if the given value is a float.
222 return isinstance(val, float)
227 """Checks if the given value is a string.
230 return isinstance(val, basestring)
233 @WithDesc("EvalToTrue")
235 """Checks if a given value evaluates to a boolean True value.
241 def TElemOf(target_list):
242 """Builds a function that checks if a given value is a member of a list.
246 return val in target_list
248 return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
254 """Checks if the given value is a list.
257 return isinstance(val, list)
262 """Checks if the given value is a tuple.
265 return isinstance(val, tuple)
268 @WithDesc("Dictionary")
270 """Checks if the given value is a dictionary.
273 return isinstance(val, dict)
277 """Check is the given container is of the given size.
281 return len(container) == size
283 return WithDesc("Length %s" % (size, ))(fn)
288 """Combine multiple functions using an AND operation.
292 return compat.all(t(val) for t in args)
294 return CombinationDesc("and", args, fn)
298 """Combine multiple functions using an OR operation.
302 return compat.any(t(val) for t in args)
304 return CombinationDesc("or", args, fn)
308 """Checks that a modified version of the argument passes the given test.
311 return WithDesc("Result of %s must be %s" %
312 (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
316 """Checks whether a string matches a specific regular expression.
318 @param pobj: Compiled regular expression as returned by C{re.compile}
321 desc = WithDesc("String matching regex \"%s\"" %
322 pobj.pattern.encode("string_escape"))
324 return desc(TAnd(TString, pobj.match))
328 """Wrap a test in a TOr(TNone, test).
330 This makes it easier to define TMaybe* types.
333 return TOr(TNone, test)
336 def TMaybeValueNone(test):
337 """Used for unsetting values.
340 return TMaybe(TOr(TValueNone, test))
345 #: a non-empty string
346 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
348 #: a maybe non-empty string
349 TMaybeString = TMaybe(TNonEmptyString)
351 #: a maybe boolean (bool or none)
352 TMaybeBool = TMaybe(TBool)
354 #: Maybe a dictionary (dict or None)
355 TMaybeDict = TMaybe(TDict)
357 #: Maybe a list (list or None)
358 TMaybeList = TMaybe(TList)
361 #: a non-negative number (value > 0)
362 # val_type should be TInt, TDouble (== TFloat), or TNumber
363 def TNonNegative(val_type):
364 return WithDesc("EqualOrGreaterThanZero")(TAnd(val_type, lambda v: v >= 0))
367 #: a positive number (value >= 0)
368 # val_type should be TInt, TDouble (== TFloat), or TNumber
369 def TPositive(val_type):
370 return WithDesc("GreaterThanZero")(TAnd(val_type, lambda v: v > 0))
373 #: a non-negative integer (value >= 0)
374 TNonNegativeInt = TNonNegative(TInt)
376 #: a positive integer (value > 0)
377 TPositiveInt = TPositive(TInt)
379 #: a maybe positive integer (positive integer or None)
380 TMaybePositiveInt = TMaybe(TPositiveInt)
382 #: a negative integer (value < 0)
384 TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
387 TNonNegativeFloat = \
388 TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
391 TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
392 TRegex(re.compile("^%s$" %
393 constants.JOB_ID_TEMPLATE))))
399 TNumber = TOr(TInt, TFloat)
402 TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
405 def TInstanceOf(cls):
406 """Checks if a given value is an instance of C{cls}.
409 @param cls: Class object
412 name = "%s.%s" % (cls.__module__, cls.__name__)
414 desc = WithDesc("Instance of %s" % (Parens(name), ))
416 return desc(lambda val: isinstance(val, cls))
419 def TListOf(my_type):
420 """Checks if a given value is a list with all elements of the same type.
423 desc = WithDesc("List of %s" % (Parens(my_type), ))
424 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
427 TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
430 def TTupleOf(*val_types):
431 """Checks if a given value is a list with the proper size and its
432 elements match the given types.
435 desc = WithDesc("Tuple of %s" % (Parens(val_types), ))
436 return desc(TAnd(TIsLength(len(val_types)), TItems(val_types)))
439 def TSetOf(val_type):
440 """Checks if a given value is a list with all elements of the same
441 type and eliminates duplicated elements.
444 desc = WithDesc("Set of %s" % (Parens(val_type), ))
445 return desc(lambda st: TListOf(val_type)(list(set(st))))
448 def TDictOf(key_type, val_type):
449 """Checks a dict type for the type of its key/values.
452 desc = WithDesc("Dictionary with keys of %s and values of %s" %
453 (Parens(key_type), Parens(val_type)))
456 return (compat.all(key_type(v) for v in container.keys()) and
457 compat.all(val_type(v) for v in container.values()))
459 return desc(TAnd(TDict, fn))
462 def _TStrictDictCheck(require_all, exclusive, items, val):
463 """Helper function for L{TStrictDict}.
466 notfound_fn = lambda _: not exclusive
468 if require_all and not frozenset(val.keys()).issuperset(items.keys()):
469 # Requires items not found in value
472 return compat.all(items.get(key, notfound_fn)(value)
473 for (key, value) in val.items())
476 def TStrictDict(require_all, exclusive, items):
477 """Strict dictionary check with specific keys.
479 @type require_all: boolean
480 @param require_all: Whether all keys in L{items} are required
481 @type exclusive: boolean
482 @param exclusive: Whether only keys listed in L{items} should be accepted
483 @type items: dictionary
484 @param items: Mapping from key (string) to verification function
487 descparts = ["Dictionary containing"]
490 descparts.append(" none but the")
493 descparts.append(" required")
496 descparts.append(" key ")
498 descparts.append(" keys ")
500 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
501 for (key, value) in items.items()))
503 desc = WithDesc("".join(descparts))
505 return desc(TAnd(TDict,
506 compat.partial(_TStrictDictCheck, require_all, exclusive,
511 """Checks individual items of a container.
513 If the verified value and the list of expected items differ in length, this
514 check considers only as many items as are contained in the shorter list. Use
515 L{TIsLength} to enforce a certain length.
518 @param items: List of checks
521 assert items, "Need items"
523 text = ["Item", "item"]
524 desc = WithDesc(utils.CommaJoin("%s %s is %s" %
525 (text[int(idx > 0)], idx, Parens(check))
526 for (idx, check) in enumerate(items)))
528 return desc(lambda value: compat.all(check(i)
529 for (check, i) in zip(items, value)))
532 TAllocPolicy = TElemOf(constants.VALID_ALLOC_POLICIES)
533 TCVErrorCode = TElemOf(constants.CV_ALL_ECODES_STRINGS)
534 TQueryResultCode = TElemOf(constants.RS_ALL)
535 TExportTarget = TOr(TNonEmptyString, TList)
536 TExportMode = TElemOf(constants.EXPORT_MODES)
537 TDiskIndex = TAnd(TNonNegativeInt, lambda val: val < constants.MAX_DISKS)
538 TReplaceDisksMode = TElemOf(constants.REPLACE_MODES)
539 TDiskTemplate = TElemOf(constants.DISK_TEMPLATES)
540 TNodeEvacMode = TElemOf(constants.IALLOCATOR_NEVAC_MODES)
541 TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)
542 TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES)
545 def TSetParamsMods(fn):
546 """Generates a check for modification lists.
550 # TODO: Remove in version 2.11 including support in LUInstanceSetParams
553 TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn]))
555 # New format, supporting adding/removing disks/NICs at arbitrary indices
557 TAnd(TIsLength(3), TItems([
558 TElemOf(constants.DDMS_VALUES_WITH_MODIFY),
559 Comment("Device index, can be negative, e.g. -1 for last disk")
560 (TOr(TInt, TString)),
564 return TOr(Comment("Recommended")(TListOf(mod_item_fn)),
565 Comment("Deprecated")(TListOf(old_mod_item_fn)))
569 Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS),
573 Comment("Disk parameters")(TDictOf(TElemOf(constants.IDISK_PARAMS),
574 TOr(TNonEmptyString, TInt)))
576 THypervisor = TElemOf(constants.HYPER_TYPES)
577 TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES)
578 TNICMode = TElemOf(constants.NIC_VALID_MODES)
579 TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES)
580 TRebootType = TElemOf(constants.REBOOT_TYPES)
581 TFileDriver = TElemOf(constants.FILE_DRIVER)
582 TOobCommand = TElemOf(constants.OOB_COMMANDS)
583 TQueryTypeOp = TElemOf(constants.QR_VIA_OP)
586 Comment("Disk parameters")(TDictOf(TNonEmptyString,
587 TOr(TNonEmptyString, TInt)))
591 TItems([Comment("Disk index")(TNonNegativeInt),
592 Comment("Parameters")(TDiskParams)]))
594 TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
597 def TStorageType(val):
598 """Builds a function that checks if a given value is a valid storage
602 return (val in constants.STORAGE_TYPES)
605 TTagKind = TElemOf(constants.VALID_TAG_TYPES)
606 TDdmSimple = TElemOf(constants.DDMS_VALUES)
607 TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
610 @WithDesc("IPv4 network")
611 def _CheckCIDRNetNotation(value):
612 """Ensure a given CIDR notation type is valid.
616 ipaddr.IPv4Network(value)
617 except ipaddr.AddressValueError:
622 @WithDesc("IPv4 address")
623 def _CheckCIDRAddrNotation(value):
624 """Ensure a given CIDR notation type is valid.
628 ipaddr.IPv4Address(value)
629 except ipaddr.AddressValueError:
634 @WithDesc("IPv6 address")
635 def _CheckCIDR6AddrNotation(value):
636 """Ensure a given CIDR notation type is valid.
640 ipaddr.IPv6Address(value)
641 except ipaddr.AddressValueError:
646 @WithDesc("IPv6 network")
647 def _CheckCIDR6NetNotation(value):
648 """Ensure a given CIDR notation type is valid.
652 ipaddr.IPv6Network(value)
653 except ipaddr.AddressValueError:
658 TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation)
659 TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation)
660 TIPv4Network = TAnd(TString, _CheckCIDRNetNotation)
661 TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
664 def TObject(val_type):
665 return TDictOf(TAny, val_type)
668 def TObjectCheck(obj, fields_types):
669 """Helper to generate type checks for objects.
671 @param obj: The object to generate type checks
672 @param fields_types: The fields and their types as a dict
673 @return: A ht type check function
676 assert set(obj.GetAllSlots()) == set(fields_types.keys()), \
677 "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys()))
678 return TStrictDict(True, True, fields_types)
682 TObjectCheck(objects.QueryFieldDefinition, {
683 "name": TNonEmptyString,
684 "title": TNonEmptyString,
685 "kind": TElemOf(constants.QFT_ALL),
686 "doc": TNonEmptyString
690 TListOf(TAnd(TIsLength(2),
691 TItems([TElemOf(constants.RS_ALL), TAny])))
693 TQueryResult = TListOf(TQueryRow)
696 TObjectCheck(objects.QueryResponse, {
697 "fields": TListOf(TQueryFieldDef),
701 TQueryFieldsResponse = \
702 TObjectCheck(objects.QueryFieldsResponse, {
703 "fields": TListOf(TQueryFieldDef)
708 TItems([Comment("success")(TBool),
709 Comment("Job ID if successful, error message"
710 " otherwise")(TOr(TString, TJobId))]))
712 TJobIdList = TListOf(TJobIdListItem)
714 TJobIdListOnly = TStrictDict(True, True, {
715 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList)
718 TInstanceMultiAllocResponse = \
719 TStrictDict(True, True, {
720 constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList),
721 constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString),
722 constants.FAILED_KEY: TListOf(TNonEmptyString)