1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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_-]+$")
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
61 __slots__ = [
62 "_fn",
63 "_text",
64 ]
65
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
79 return self._fn(*args)
80
83 """Wrapper class for description text.
84
85 """
88
90 return "<%s %r>" % (self._text, self._fn)
91
99
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
125
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
139
140
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
153
154
155 @WithDesc(str([]))
156 -def EmptyList():
157 """Returns an empty list.
158
159 """
160 return []
161
165 """Returns an empty dict.
166
167 """
168 return {}
169
170
171
172 NoDefault = object()
173
174
175
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
222
223
224
225
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
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
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
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
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
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
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
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
355
356
357
358
359
360 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
361
362
363 TMaybeString = TMaybe(TNonEmptyString)
364
365
366 TMaybeBool = TMaybe(TBool)
367
368
369 TMaybeDict = TMaybe(TDict)
370
371
372 TMaybeList = TMaybe(TList)
378 return WithDesc("EqualOrGreaterThanZero")(TAnd(val_type, lambda v: v >= 0))
379
384 return WithDesc("GreaterThanZero")(TAnd(val_type, lambda v: v > 0))
385
386
387
388 TNonNegativeInt = TNonNegative(TInt)
389
390
391 TPositiveInt = TPositive(TInt)
392
393
394 TMaybePositiveInt = TMaybe(TPositiveInt)
395
396
397 TNegativeInt = \
398 TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
399
400
401 TNonNegativeFloat = \
402 TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
403
404
405 TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
406 TRegex(re.compile("^%s$" %
407 constants.JOB_ID_TEMPLATE))))
408
409
410 TDouble = TFloat
411
412
413 TNumber = TOr(TInt, TFloat)
414
415
416 TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
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
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
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
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))
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
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
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
511 return False
512
513 return compat.all(items.get(key, notfound_fn)(value)
514 for (key, value) in val.items())
515
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
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)
590 """Generates a check for modification lists.
591
592 """
593
594
595 old_mod_item_fn = \
596 TAnd(TIsLength(2),
597 TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn]))
598
599
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
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))
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)
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
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
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
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)
713
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