Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 7d81bb8b

History | View | Annotate | Download (17.1 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
def TSetSnapParams(fn):
569
  return  TListOf(TItems([TOr(TInt, TString), fn]))
570

    
571

    
572
TINicParams = \
573
    Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS),
574
                                      TMaybe(TString)))
575

    
576
TIDiskParams = \
577
    Comment("Disk parameters")(TDictOf(TNonEmptyString,
578
                                       TOr(TNonEmptyString, TInt)))
579

    
580
TISnapParams = \
581
    Comment("Snapshot parameters")(
582
      TDictOf(TElemOf(constants.IDISK_SNAPSHOT_NAME),
583
              TNonEmptyString))
584

    
585
THypervisor = TElemOf(constants.HYPER_TYPES)
586
TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES)
587
TNICMode = TElemOf(constants.NIC_VALID_MODES)
588
TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES)
589
TRebootType = TElemOf(constants.REBOOT_TYPES)
590
TFileDriver = TElemOf(constants.FILE_DRIVER)
591
TOobCommand = TElemOf(constants.OOB_COMMANDS)
592
TQueryTypeOp = TElemOf(constants.QR_VIA_OP)
593

    
594
TDiskParams = \
595
    Comment("Disk parameters")(TDictOf(TNonEmptyString,
596
                                       TOr(TNonEmptyString, TInt)))
597

    
598
TDiskChanges = \
599
    TAnd(TIsLength(2),
600
         TItems([Comment("Disk index")(TNonNegativeInt),
601
                 Comment("Parameters")(TDiskParams)]))
602

    
603
TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
604

    
605

    
606
def TStorageType(val):
607
  """Builds a function that checks if a given value is a valid storage
608
  type.
609

610
  """
611
  return (val in constants.STORAGE_TYPES)
612

    
613

    
614
TTagKind = TElemOf(constants.VALID_TAG_TYPES)
615
TDdmSimple = TElemOf(constants.DDMS_VALUES)
616
TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
617

    
618

    
619
@WithDesc("IPv4 network")
620
def _CheckCIDRNetNotation(value):
621
  """Ensure a given CIDR notation type is valid.
622

623
  """
624
  try:
625
    ipaddr.IPv4Network(value)
626
  except ipaddr.AddressValueError:
627
    return False
628
  return True
629

    
630

    
631
@WithDesc("IPv4 address")
632
def _CheckCIDRAddrNotation(value):
633
  """Ensure a given CIDR notation type is valid.
634

635
  """
636
  try:
637
    ipaddr.IPv4Address(value)
638
  except ipaddr.AddressValueError:
639
    return False
640
  return True
641

    
642

    
643
@WithDesc("IPv6 address")
644
def _CheckCIDR6AddrNotation(value):
645
  """Ensure a given CIDR notation type is valid.
646

647
  """
648
  try:
649
    ipaddr.IPv6Address(value)
650
  except ipaddr.AddressValueError:
651
    return False
652
  return True
653

    
654

    
655
@WithDesc("IPv6 network")
656
def _CheckCIDR6NetNotation(value):
657
  """Ensure a given CIDR notation type is valid.
658

659
  """
660
  try:
661
    ipaddr.IPv6Network(value)
662
  except ipaddr.AddressValueError:
663
    return False
664
  return True
665

    
666

    
667
TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation)
668
TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation)
669
TIPv4Network = TAnd(TString, _CheckCIDRNetNotation)
670
TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
671

    
672

    
673
def TObject(val_type):
674
  return TDictOf(TAny, val_type)
675

    
676

    
677
def TObjectCheck(obj, fields_types):
678
  """Helper to generate type checks for objects.
679

680
  @param obj: The object to generate type checks
681
  @param fields_types: The fields and their types as a dict
682
  @return: A ht type check function
683

684
  """
685
  assert set(obj.GetAllSlots()) == set(fields_types.keys()), \
686
    "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys()))
687
  return TStrictDict(True, True, fields_types)
688

    
689

    
690
TQueryFieldDef = \
691
    TObjectCheck(objects.QueryFieldDefinition, {
692
        "name": TNonEmptyString,
693
        "title": TNonEmptyString,
694
        "kind": TElemOf(constants.QFT_ALL),
695
        "doc": TNonEmptyString
696
    })
697

    
698
TQueryRow = \
699
    TListOf(TAnd(TIsLength(2),
700
                 TItems([TElemOf(constants.RS_ALL), TAny])))
701

    
702
TQueryResult = TListOf(TQueryRow)
703

    
704
TQueryResponse = \
705
    TObjectCheck(objects.QueryResponse, {
706
        "fields": TListOf(TQueryFieldDef),
707
        "data": TQueryResult
708
    })
709

    
710
TQueryFieldsResponse = \
711
    TObjectCheck(objects.QueryFieldsResponse, {
712
        "fields": TListOf(TQueryFieldDef)
713
    })
714

    
715
TJobIdListItem = \
716
    TAnd(TIsLength(2),
717
         TItems([Comment("success")(TBool),
718
                 Comment("Job ID if successful, error message"
719
                         " otherwise")(TOr(TString, TJobId))]))
720

    
721
TJobIdList = TListOf(TJobIdListItem)
722

    
723
TJobIdListOnly = TStrictDict(True, True, {
724
  constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList)
725
  })
726

    
727
TInstanceMultiAllocResponse = \
728
    TStrictDict(True, True, {
729
      constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList),
730
      constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString),
731
      constants.FAILED_KEY: TListOf(TNonEmptyString)
732
    })