Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 84ad6b78

History | View | Annotate | Download (16.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
20

    
21

    
22
"""Module implementing the parameter types code."""
23

    
24
import re
25
import operator
26
import ipaddr
27

    
28
from ganeti import compat
29
from ganeti import utils
30
from ganeti import constants
31
from ganeti import objects
32

    
33

    
34
_PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
35

    
36

    
37
def Parens(text):
38
  """Enclose text in parens if necessary.
39

40
  @param text: Text
41

42
  """
43
  text = str(text)
44

    
45
  if _PAREN_RE.match(text):
46
    return text
47
  else:
48
    return "(%s)" % text
49

    
50

    
51
class _WrapperBase(object):
52
  __slots__ = [
53
    "_fn",
54
    "_text",
55
    ]
56

    
57
  def __init__(self, text, fn):
58
    """Initializes this class.
59

60
    @param text: Description
61
    @param fn: Wrapped function
62

63
    """
64
    assert text.strip()
65

    
66
    self._text = text
67
    self._fn = fn
68

    
69
  def __call__(self, *args):
70
    return self._fn(*args)
71

    
72

    
73
class _DescWrapper(_WrapperBase):
74
  """Wrapper class for description text.
75

76
  """
77
  def __str__(self):
78
    return self._text
79

    
80

    
81
class _CommentWrapper(_WrapperBase):
82
  """Wrapper class for comment.
83

84
  """
85
  def __str__(self):
86
    return "%s [%s]" % (self._fn, self._text)
87

    
88

    
89
def WithDesc(text):
90
  """Builds wrapper class with description text.
91

92
  @type text: string
93
  @param text: Description text
94
  @return: Callable class
95

96
  """
97
  assert text[0] == text[0].upper()
98

    
99
  return compat.partial(_DescWrapper, text)
100

    
101

    
102
def Comment(text):
103
  """Builds wrapper for adding comment to description text.
104

105
  @type text: string
106
  @param text: Comment text
107
  @return: Callable class
108

109
  """
110
  assert not frozenset(text).intersection("[]")
111

    
112
  return compat.partial(_CommentWrapper, text)
113

    
114

    
115
def CombinationDesc(op, args, fn):
116
  """Build description for combinating operator.
117

118
  @type op: string
119
  @param op: Operator as text (e.g. "and")
120
  @type args: list
121
  @param args: Operator arguments
122
  @type fn: callable
123
  @param fn: Wrapped function
124

125
  """
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")
131

    
132
  if len(args) == 1:
133
    descr = str(args[0])
134
  else:
135
    descr = (" %s " % op).join(Parens(i) for i in args)
136

    
137
  return WithDesc(descr)(fn)
138

    
139

    
140
# Modifiable default values; need to define these here before the
141
# actual LUs
142

    
143
@WithDesc(str([]))
144
def EmptyList():
145
  """Returns an empty list.
146

147
  """
148
  return []
149

    
150

    
151
@WithDesc(str({}))
152
def EmptyDict():
153
  """Returns an empty dict.
154

155
  """
156
  return {}
157

    
158

    
159
#: The without-default default value
160
NoDefault = object()
161

    
162

    
163
# Some basic types
164
@WithDesc("Anything")
165
def TAny(_):
166
  """Accepts any value.
167

168
  """
169
  return True
170

    
171

    
172
@WithDesc("NotNone")
173
def TNotNone(val):
174
  """Checks if the given value is not None.
175

176
  """
177
  return val is not None
178

    
179

    
180
@WithDesc("None")
181
def TNone(val):
182
  """Checks if the given value is None.
183

184
  """
185
  return val is None
186

    
187

    
188
@WithDesc("ValueNone")
189
def TValueNone(val):
190
  """Checks if the given value is L{constants.VALUE_NONE}.
191

192
  """
193
  return val == constants.VALUE_NONE
194

    
195

    
196
@WithDesc("Boolean")
197
def TBool(val):
198
  """Checks if the given value is a boolean.
199

200
  """
201
  return isinstance(val, bool)
202

    
203

    
204
@WithDesc("Integer")
205
def TInt(val):
206
  """Checks if the given value is an integer.
207

208
  """
209
  # For backwards compatibility with older Python versions, boolean values are
210
  # also integers and should be excluded in this test.
211
  #
212
  # >>> (isinstance(False, int), isinstance(True, int))
213
  # (True, True)
214
  return isinstance(val, (int, long)) and not isinstance(val, bool)
215

    
216

    
217
@WithDesc("Float")
218
def TFloat(val):
219
  """Checks if the given value is a float.
220

221
  """
222
  return isinstance(val, float)
223

    
224

    
225
@WithDesc("String")
226
def TString(val):
227
  """Checks if the given value is a string.
228

229
  """
230
  return isinstance(val, basestring)
231

    
232

    
233
@WithDesc("EvalToTrue")
234
def TTrue(val):
235
  """Checks if a given value evaluates to a boolean True value.
236

237
  """
238
  return bool(val)
239

    
240

    
241
def TElemOf(target_list):
242
  """Builds a function that checks if a given value is a member of a list.
243

244
  """
245
  def fn(val):
246
    return val in target_list
247

    
248
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
249

    
250

    
251
# Container types
252
@WithDesc("List")
253
def TList(val):
254
  """Checks if the given value is a list.
255

256
  """
257
  return isinstance(val, list)
258

    
259

    
260
@WithDesc("Tuple")
261
def TTuple(val):
262
  """Checks if the given value is a tuple.
263

264
  """
265
  return isinstance(val, tuple)
266

    
267

    
268
@WithDesc("Dictionary")
269
def TDict(val):
270
  """Checks if the given value is a dictionary.
271

272
  """
273
  return isinstance(val, dict)
274

    
275

    
276
def TIsLength(size):
277
  """Check is the given container is of the given size.
278

279
  """
280
  def fn(container):
281
    return len(container) == size
282

    
283
  return WithDesc("Length %s" % (size, ))(fn)
284

    
285

    
286
# Combinator types
287
def TAnd(*args):
288
  """Combine multiple functions using an AND operation.
289

290
  """
291
  def fn(val):
292
    return compat.all(t(val) for t in args)
293

    
294
  return CombinationDesc("and", args, fn)
295

    
296

    
297
def TOr(*args):
298
  """Combine multiple functions using an OR operation.
299

300
  """
301
  def fn(val):
302
    return compat.any(t(val) for t in args)
303

    
304
  return CombinationDesc("or", args, fn)
305

    
306

    
307
def TMap(fn, test):
308
  """Checks that a modified version of the argument passes the given test.
309

310
  """
311
  return WithDesc("Result of %s must be %s" %
312
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
313

    
314

    
315
def TRegex(pobj):
316
  """Checks whether a string matches a specific regular expression.
317

318
  @param pobj: Compiled regular expression as returned by C{re.compile}
319

320
  """
321
  desc = WithDesc("String matching regex \"%s\"" %
322
                  pobj.pattern.encode("string_escape"))
323

    
324
  return desc(TAnd(TString, pobj.match))
325

    
326

    
327
def TMaybe(test):
328
  """Wrap a test in a TOr(TNone, test).
329

330
  This makes it easier to define TMaybe* types.
331

332
  """
333
  return TOr(TNone, test)
334

    
335

    
336
def TMaybeValueNone(test):
337
  """Used for unsetting values.
338

339
  """
340
  return TMaybe(TOr(TValueNone, test))
341

    
342

    
343
# Type aliases
344

    
345
#: a non-empty string
346
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
347

    
348
#: a maybe non-empty string
349
TMaybeString = TMaybe(TNonEmptyString)
350

    
351
#: a maybe boolean (bool or none)
352
TMaybeBool = TMaybe(TBool)
353

    
354
#: Maybe a dictionary (dict or None)
355
TMaybeDict = TMaybe(TDict)
356

    
357
#: Maybe a list (list or None)
358
TMaybeList = TMaybe(TList)
359

    
360

    
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))
365

    
366

    
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))
371

    
372

    
373
#: a non-negative integer (value >= 0)
374
TNonNegativeInt = TNonNegative(TInt)
375

    
376
#: a positive integer (value > 0)
377
TPositiveInt = TPositive(TInt)
378

    
379
#: a maybe positive integer (positive integer or None)
380
TMaybePositiveInt = TMaybe(TPositiveInt)
381

    
382
#: a negative integer (value < 0)
383
TNegativeInt = \
384
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
385

    
386
#: a positive float
387
TNonNegativeFloat = \
388
  TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
389

    
390
#: Job ID
391
TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
392
                               TRegex(re.compile("^%s$" %
393
                                                 constants.JOB_ID_TEMPLATE))))
394

    
395
#: Double (== Float)
396
TDouble = TFloat
397

    
398
#: Number
399
TNumber = TOr(TInt, TFloat)
400

    
401
#: Relative job ID
402
TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
403

    
404

    
405
def TInstanceOf(cls):
406
  """Checks if a given value is an instance of C{cls}.
407

408
  @type cls: class
409
  @param cls: Class object
410

411
  """
412
  name = "%s.%s" % (cls.__module__, cls.__name__)
413

    
414
  desc = WithDesc("Instance of %s" % (Parens(name), ))
415

    
416
  return desc(lambda val: isinstance(val, cls))
417

    
418

    
419
def TListOf(my_type):
420
  """Checks if a given value is a list with all elements of the same type.
421

422
  """
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)))
425

    
426

    
427
TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
428

    
429

    
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.
433

434
  """
435
  desc = WithDesc("Tuple of %s" % (Parens(val_types), ))
436
  return desc(TAnd(TIsLength(len(val_types)), TItems(val_types)))
437

    
438

    
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.
442

443
  """
444
  desc = WithDesc("Set of %s" % (Parens(val_type), ))
445
  return desc(lambda st: TListOf(val_type)(list(set(st))))
446

    
447

    
448
def TDictOf(key_type, val_type):
449
  """Checks a dict type for the type of its key/values.
450

451
  """
452
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
453
                  (Parens(key_type), Parens(val_type)))
454

    
455
  def fn(container):
456
    return (compat.all(key_type(v) for v in container.keys()) and
457
            compat.all(val_type(v) for v in container.values()))
458

    
459
  return desc(TAnd(TDict, fn))
460

    
461

    
462
def _TStrictDictCheck(require_all, exclusive, items, val):
463
  """Helper function for L{TStrictDict}.
464

465
  """
466
  notfound_fn = lambda _: not exclusive
467

    
468
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
469
    # Requires items not found in value
470
    return False
471

    
472
  return compat.all(items.get(key, notfound_fn)(value)
473
                    for (key, value) in val.items())
474

    
475

    
476
def TStrictDict(require_all, exclusive, items):
477
  """Strict dictionary check with specific keys.
478

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
485

486
  """
487
  descparts = ["Dictionary containing"]
488

    
489
  if exclusive:
490
    descparts.append(" none but the")
491

    
492
  if require_all:
493
    descparts.append(" required")
494

    
495
  if len(items) == 1:
496
    descparts.append(" key ")
497
  else:
498
    descparts.append(" keys ")
499

    
500
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
501
                                   for (key, value) in items.items()))
502

    
503
  desc = WithDesc("".join(descparts))
504

    
505
  return desc(TAnd(TDict,
506
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
507
                                  items)))
508

    
509

    
510
def TItems(items):
511
  """Checks individual items of a container.
512

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.
516

517
  @type items: list
518
  @param items: List of checks
519

520
  """
521
  assert items, "Need items"
522

    
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)))
527

    
528
  return desc(lambda value: compat.all(check(i)
529
                                       for (check, i) in zip(items, value)))
530

    
531

    
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
TEvacMode = TElemOf(constants.NODE_EVAC_MODES)
541
TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)
542
TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES)
543

    
544

    
545
def TSetParamsMods(fn):
546
  """Generates a check for modification lists.
547

548
  """
549
  # Old format
550
  # TODO: Remove in version 2.11 including support in LUInstanceSetParams
551
  old_mod_item_fn = \
552
    TAnd(TIsLength(2),
553
         TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn]))
554

    
555
  # New format, supporting adding/removing disks/NICs at arbitrary indices
556
  mod_item_fn = \
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)),
561
        fn,
562
        ]))
563

    
564
  return TOr(Comment("Recommended")(TListOf(mod_item_fn)),
565
             Comment("Deprecated")(TListOf(old_mod_item_fn)))
566

    
567

    
568
TINicParams = \
569
    Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS),
570
                                      TMaybe(TString)))
571

    
572
TIDiskParams = \
573
    Comment("Disk parameters")(TDictOf(TElemOf(constants.IDISK_PARAMS),
574
                                       TOr(TNonEmptyString, TInt)))
575

    
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)
584

    
585
TDiskParams = \
586
    Comment("Disk parameters")(TDictOf(TNonEmptyString,
587
                                       TOr(TNonEmptyString, TInt)))
588

    
589
TDiskChanges = \
590
    TAnd(TIsLength(2),
591
         TItems([Comment("Disk index")(TNonNegativeInt),
592
                 Comment("Parameters")(TDiskParams)]))
593

    
594
TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
595

    
596

    
597
def TStorageType(val):
598
  """Builds a function that checks if a given value is a valid storage
599
  type.
600

601
  """
602
  return (val in constants.STORAGE_TYPES)
603

    
604

    
605
TTagKind = TElemOf(constants.VALID_TAG_TYPES)
606
TDdmSimple = TElemOf(constants.DDMS_VALUES)
607
TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
608

    
609

    
610
@WithDesc("IPv4 network")
611
def _CheckCIDRNetNotation(value):
612
  """Ensure a given CIDR notation type is valid.
613

614
  """
615
  try:
616
    ipaddr.IPv4Network(value)
617
  except ipaddr.AddressValueError:
618
    return False
619
  return True
620

    
621

    
622
@WithDesc("IPv4 address")
623
def _CheckCIDRAddrNotation(value):
624
  """Ensure a given CIDR notation type is valid.
625

626
  """
627
  try:
628
    ipaddr.IPv4Address(value)
629
  except ipaddr.AddressValueError:
630
    return False
631
  return True
632

    
633

    
634
@WithDesc("IPv6 address")
635
def _CheckCIDR6AddrNotation(value):
636
  """Ensure a given CIDR notation type is valid.
637

638
  """
639
  try:
640
    ipaddr.IPv6Address(value)
641
  except ipaddr.AddressValueError:
642
    return False
643
  return True
644

    
645

    
646
@WithDesc("IPv6 network")
647
def _CheckCIDR6NetNotation(value):
648
  """Ensure a given CIDR notation type is valid.
649

650
  """
651
  try:
652
    ipaddr.IPv6Network(value)
653
  except ipaddr.AddressValueError:
654
    return False
655
  return True
656

    
657

    
658
TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation)
659
TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation)
660
TIPv4Network = TAnd(TString, _CheckCIDRNetNotation)
661
TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
662

    
663

    
664
def TObject(val_type):
665
  return TDictOf(TAny, val_type)
666

    
667

    
668
def TObjectCheck(obj, fields_types):
669
  """Helper to generate type checks for objects.
670

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
674

675
  """
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)
679

    
680

    
681
TQueryFieldDef = \
682
    TObjectCheck(objects.QueryFieldDefinition, {
683
        "name": TNonEmptyString,
684
        "title": TNonEmptyString,
685
        "kind": TElemOf(constants.QFT_ALL),
686
        "doc": TNonEmptyString
687
    })
688

    
689
TQueryRow = \
690
    TListOf(TAnd(TIsLength(2),
691
                 TItems([TElemOf(constants.RS_ALL), TAny])))
692

    
693
TQueryResult = TListOf(TQueryRow)
694

    
695
TQueryResponse = \
696
    TObjectCheck(objects.QueryResponse, {
697
        "fields": TListOf(TQueryFieldDef),
698
        "data": TQueryResult
699
    })
700

    
701
TQueryFieldsResponse = \
702
    TObjectCheck(objects.QueryFieldsResponse, {
703
        "fields": TListOf(TQueryFieldDef)
704
    })
705

    
706
TJobIdListItem = \
707
    TAnd(TIsLength(2),
708
         TItems([Comment("success")(TBool),
709
                 Comment("Job ID if successful, error message"
710
                         " otherwise")(TOr(TString, TJobId))]))
711

    
712
TJobIdList = TListOf(TJobIdListItem)
713

    
714
TJobIdListOnly = TStrictDict(True, True, {
715
  constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList)
716
  })
717

    
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)
723
    })