Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 31d3b918

History | View | Annotate | Download (17.5 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
from ganeti.serializer import Private
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
  def __repr__(self):
81
    return "<%s %r>" % (self._text, self._fn)
82

    
83

    
84
class _CommentWrapper(_WrapperBase):
85
  """Wrapper class for comment.
86

87
  """
88
  def __str__(self):
89
    return "%s [%s]" % (self._fn, self._text)
90

    
91

    
92
def WithDesc(text):
93
  """Builds wrapper class with description text.
94

95
  @type text: string
96
  @param text: Description text
97
  @return: Callable class
98

99
  """
100
  assert text[0] == text[0].upper()
101

    
102
  return compat.partial(_DescWrapper, text)
103

    
104

    
105
def Comment(text):
106
  """Builds wrapper for adding comment to description text.
107

108
  @type text: string
109
  @param text: Comment text
110
  @return: Callable class
111

112
  """
113
  assert not frozenset(text).intersection("[]")
114

    
115
  return compat.partial(_CommentWrapper, text)
116

    
117

    
118
def CombinationDesc(op, args, fn):
119
  """Build description for combinating operator.
120

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

128
  """
129
  # Some type descriptions are rather long. If "None" is listed at the
130
  # end or somewhere in between it is easily missed. Therefore it should
131
  # be at the beginning, e.g. "None or (long description)".
132
  if __debug__ and TNone in args and args.index(TNone) > 0:
133
    raise Exception("TNone must be listed first")
134

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

    
140
  return WithDesc(descr)(fn)
141

    
142

    
143
# Modifiable default values; need to define these here before the
144
# actual LUs
145

    
146
@WithDesc(str([]))
147
def EmptyList():
148
  """Returns an empty list.
149

150
  """
151
  return []
152

    
153

    
154
@WithDesc(str({}))
155
def EmptyDict():
156
  """Returns an empty dict.
157

158
  """
159
  return {}
160

    
161

    
162
#: The without-default default value
163
NoDefault = object()
164

    
165

    
166
# Some basic types
167
@WithDesc("Anything")
168
def TAny(_):
169
  """Accepts any value.
170

171
  """
172
  return True
173

    
174

    
175
@WithDesc("NotNone")
176
def TNotNone(val):
177
  """Checks if the given value is not None.
178

179
  """
180
  return val is not None
181

    
182

    
183
@WithDesc("None")
184
def TNone(val):
185
  """Checks if the given value is None.
186

187
  """
188
  return val is None
189

    
190

    
191
@WithDesc("ValueNone")
192
def TValueNone(val):
193
  """Checks if the given value is L{constants.VALUE_NONE}.
194

195
  """
196
  return val == constants.VALUE_NONE
197

    
198

    
199
@WithDesc("Boolean")
200
def TBool(val):
201
  """Checks if the given value is a boolean.
202

203
  """
204
  return isinstance(val, bool)
205

    
206

    
207
@WithDesc("Integer")
208
def TInt(val):
209
  """Checks if the given value is an integer.
210

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

    
219

    
220
@WithDesc("Float")
221
def TFloat(val):
222
  """Checks if the given value is a float.
223

224
  """
225
  return isinstance(val, float)
226

    
227

    
228
@WithDesc("String")
229
def TString(val):
230
  """Checks if the given value is a string.
231

232
  """
233
  return isinstance(val, basestring)
234

    
235

    
236
@WithDesc("EvalToTrue")
237
def TTrue(val):
238
  """Checks if a given value evaluates to a boolean True value.
239

240
  """
241
  return bool(val)
242

    
243

    
244
def TElemOf(target_list):
245
  """Builds a function that checks if a given value is a member of a list.
246

247
  """
248
  def fn(val):
249
    return val in target_list
250

    
251
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
252

    
253

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

259
  """
260
  return isinstance(val, list)
261

    
262

    
263
@WithDesc("Tuple")
264
def TTuple(val):
265
  """Checks if the given value is a tuple.
266

267
  """
268
  return isinstance(val, tuple)
269

    
270

    
271
@WithDesc("Dictionary")
272
def TDict(val):
273
  """Checks if the given value is a dictionary.
274

275
  Note that L{PrivateDict}s subclass dict and pass this check.
276

277
  """
278
  return isinstance(val, dict)
279

    
280

    
281
def TIsLength(size):
282
  """Check is the given container is of the given size.
283

284
  """
285
  def fn(container):
286
    return len(container) == size
287

    
288
  return WithDesc("Length %s" % (size, ))(fn)
289

    
290

    
291
# Combinator types
292
def TAnd(*args):
293
  """Combine multiple functions using an AND operation.
294

295
  """
296
  def fn(val):
297
    return compat.all(t(val) for t in args)
298

    
299
  return CombinationDesc("and", args, fn)
300

    
301

    
302
def TOr(*args):
303
  """Combine multiple functions using an OR operation.
304

305
  """
306
  def fn(val):
307
    return compat.any(t(val) for t in args)
308

    
309
  return CombinationDesc("or", args, fn)
310

    
311

    
312
def TMap(fn, test):
313
  """Checks that a modified version of the argument passes the given test.
314

315
  """
316
  return WithDesc("Result of %s must be %s" %
317
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
318

    
319

    
320
def TRegex(pobj):
321
  """Checks whether a string matches a specific regular expression.
322

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

325
  """
326
  desc = WithDesc("String matching regex \"%s\"" %
327
                  pobj.pattern.encode("string_escape"))
328

    
329
  return desc(TAnd(TString, pobj.match))
330

    
331

    
332
def TMaybe(test):
333
  """Wrap a test in a TOr(TNone, test).
334

335
  This makes it easier to define TMaybe* types.
336

337
  """
338
  return TOr(TNone, test)
339

    
340

    
341
def TMaybeValueNone(test):
342
  """Used for unsetting values.
343

344
  """
345
  return TMaybe(TOr(TValueNone, test))
346

    
347

    
348
# Type aliases
349

    
350
#: a non-empty string
351
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
352

    
353
#: a maybe non-empty string
354
TMaybeString = TMaybe(TNonEmptyString)
355

    
356
#: a maybe boolean (bool or none)
357
TMaybeBool = TMaybe(TBool)
358

    
359
#: Maybe a dictionary (dict or None)
360
TMaybeDict = TMaybe(TDict)
361

    
362
#: Maybe a list (list or None)
363
TMaybeList = TMaybe(TList)
364

    
365

    
366
#: a non-negative number (value > 0)
367
# val_type should be TInt, TDouble (== TFloat), or TNumber
368
def TNonNegative(val_type):
369
  return WithDesc("EqualOrGreaterThanZero")(TAnd(val_type, lambda v: v >= 0))
370

    
371

    
372
#: a positive number (value >= 0)
373
# val_type should be TInt, TDouble (== TFloat), or TNumber
374
def TPositive(val_type):
375
  return WithDesc("GreaterThanZero")(TAnd(val_type, lambda v: v > 0))
376

    
377

    
378
#: a non-negative integer (value >= 0)
379
TNonNegativeInt = TNonNegative(TInt)
380

    
381
#: a positive integer (value > 0)
382
TPositiveInt = TPositive(TInt)
383

    
384
#: a maybe positive integer (positive integer or None)
385
TMaybePositiveInt = TMaybe(TPositiveInt)
386

    
387
#: a negative integer (value < 0)
388
TNegativeInt = \
389
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
390

    
391
#: a positive float
392
TNonNegativeFloat = \
393
  TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
394

    
395
#: Job ID
396
TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
397
                               TRegex(re.compile("^%s$" %
398
                                                 constants.JOB_ID_TEMPLATE))))
399

    
400
#: Double (== Float)
401
TDouble = TFloat
402

    
403
#: Number
404
TNumber = TOr(TInt, TFloat)
405

    
406
#: Relative job ID
407
TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
408

    
409

    
410
def TInstanceOf(cls):
411
  """Checks if a given value is an instance of C{cls}.
412

413
  @type cls: class
414
  @param cls: Class object
415

416
  """
417
  name = "%s.%s" % (cls.__module__, cls.__name__)
418

    
419
  desc = WithDesc("Instance of %s" % (Parens(name), ))
420

    
421
  return desc(lambda val: isinstance(val, cls))
422

    
423

    
424
def TPrivate(val_type):
425
  """Checks if a given value is an instance of Private.
426

427
  """
428
  def fn(val):
429
    return isinstance(val, Private) and val_type(val.Get())
430

    
431
  desc = WithDesc("Private %s" % Parens(val_type))
432

    
433
  return desc(fn)
434

    
435

    
436
def TListOf(my_type):
437
  """Checks if a given value is a list with all elements of the same type.
438

439
  """
440
  desc = WithDesc("List of %s" % (Parens(my_type), ))
441
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
442

    
443

    
444
TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
445

    
446

    
447
def TTupleOf(*val_types):
448
  """Checks if a given value is a list with the proper size and its
449
     elements match the given types.
450

451
  """
452
  desc = WithDesc("Tuple of %s" % (Parens(val_types), ))
453
  return desc(TAnd(TIsLength(len(val_types)), TItems(val_types)))
454

    
455

    
456
def TSetOf(val_type):
457
  """Checks if a given value is a list with all elements of the same
458
     type and eliminates duplicated elements.
459

460
  """
461
  desc = WithDesc("Set of %s" % (Parens(val_type), ))
462
  return desc(lambda st: TListOf(val_type)(list(set(st))))
463

    
464

    
465
def TDictOf(key_type, val_type):
466
  """Checks a dict type for the type of its key/values.
467

468
  """
469
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
470
                  (Parens(key_type), Parens(val_type)))
471

    
472
  def fn(container):
473
    return (compat.all(key_type(v) for v in container.keys()) and
474
            compat.all(val_type(v) for v in container.values()))
475

    
476
  return desc(TAnd(TDict, fn))
477

    
478

    
479
def _TStrictDictCheck(require_all, exclusive, items, val):
480
  """Helper function for L{TStrictDict}.
481

482
  """
483
  notfound_fn = lambda _: not exclusive
484

    
485
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
486
    # Requires items not found in value
487
    return False
488

    
489
  return compat.all(items.get(key, notfound_fn)(value)
490
                    for (key, value) in val.items())
491

    
492

    
493
def TStrictDict(require_all, exclusive, items):
494
  """Strict dictionary check with specific keys.
495

496
  @type require_all: boolean
497
  @param require_all: Whether all keys in L{items} are required
498
  @type exclusive: boolean
499
  @param exclusive: Whether only keys listed in L{items} should be accepted
500
  @type items: dictionary
501
  @param items: Mapping from key (string) to verification function
502

503
  """
504
  descparts = ["Dictionary containing"]
505

    
506
  if exclusive:
507
    descparts.append(" none but the")
508

    
509
  if require_all:
510
    descparts.append(" required")
511

    
512
  if len(items) == 1:
513
    descparts.append(" key ")
514
  else:
515
    descparts.append(" keys ")
516

    
517
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
518
                                   for (key, value) in items.items()))
519

    
520
  desc = WithDesc("".join(descparts))
521

    
522
  return desc(TAnd(TDict,
523
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
524
                                  items)))
525

    
526

    
527
def TItems(items):
528
  """Checks individual items of a container.
529

530
  If the verified value and the list of expected items differ in length, this
531
  check considers only as many items as are contained in the shorter list. Use
532
  L{TIsLength} to enforce a certain length.
533

534
  @type items: list
535
  @param items: List of checks
536

537
  """
538
  assert items, "Need items"
539

    
540
  text = ["Item", "item"]
541
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
542
                                  (text[int(idx > 0)], idx, Parens(check))
543
                                  for (idx, check) in enumerate(items)))
544

    
545
  return desc(lambda value: compat.all(check(i)
546
                                       for (check, i) in zip(items, value)))
547

    
548

    
549
TAllocPolicy = TElemOf(constants.VALID_ALLOC_POLICIES)
550
TCVErrorCode = TElemOf(constants.CV_ALL_ECODES_STRINGS)
551
TQueryResultCode = TElemOf(constants.RS_ALL)
552
TExportTarget = TOr(TNonEmptyString, TList)
553
TExportMode = TElemOf(constants.EXPORT_MODES)
554
TDiskIndex = TAnd(TNonNegativeInt, lambda val: val < constants.MAX_DISKS)
555
TReplaceDisksMode = TElemOf(constants.REPLACE_MODES)
556
TDiskTemplate = TElemOf(constants.DISK_TEMPLATES)
557
TEvacMode = TElemOf(constants.NODE_EVAC_MODES)
558
TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)
559
TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES)
560
TImportExportCompression = TElemOf(constants.IEC_ALL)
561

    
562

    
563
def TSetParamsMods(fn):
564
  """Generates a check for modification lists.
565

566
  """
567
  # Old format
568
  # TODO: Remove in version 2.11 including support in LUInstanceSetParams
569
  old_mod_item_fn = \
570
    TAnd(TIsLength(2),
571
         TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn]))
572

    
573
  # New format, supporting adding/removing disks/NICs at arbitrary indices
574
  mod_item_fn = \
575
      TAnd(TIsLength(3), TItems([
576
        TElemOf(constants.DDMS_VALUES_WITH_MODIFY),
577
        Comment("Device index, can be negative, e.g. -1 for last disk")
578
                 (TOr(TInt, TString)),
579
        fn,
580
        ]))
581

    
582
  return TOr(Comment("Recommended")(TListOf(mod_item_fn)),
583
             Comment("Deprecated")(TListOf(old_mod_item_fn)))
584

    
585

    
586
TINicParams = \
587
    Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS),
588
                                      TMaybe(TString)))
589

    
590
TIDiskParams = \
591
    Comment("Disk parameters")(TDictOf(TNonEmptyString,
592
                                       TOr(TNonEmptyString, TInt)))
593

    
594
THypervisor = TElemOf(constants.HYPER_TYPES)
595
TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES)
596
TNICMode = TElemOf(constants.NIC_VALID_MODES)
597
TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES)
598
TRebootType = TElemOf(constants.REBOOT_TYPES)
599
TFileDriver = TElemOf(constants.FILE_DRIVER)
600
TOobCommand = TElemOf(constants.OOB_COMMANDS)
601
# FIXME: adjust this after all queries are in haskell
602
TQueryTypeOp = TElemOf(set(constants.QR_VIA_OP)
603
                       .union(set(constants.QR_VIA_LUXI)))
604

    
605
TDiskParams = \
606
    Comment("Disk parameters")(TDictOf(TNonEmptyString,
607
                                       TOr(TNonEmptyString, TInt)))
608

    
609
TDiskChanges = \
610
    TAnd(TIsLength(2),
611
         TItems([Comment("Disk index")(TNonNegativeInt),
612
                 Comment("Parameters")(TDiskParams)]))
613

    
614
TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
615

    
616

    
617
def TStorageType(val):
618
  """Builds a function that checks if a given value is a valid storage
619
  type.
620

621
  """
622
  return (val in constants.STORAGE_TYPES)
623

    
624

    
625
TTagKind = TElemOf(constants.VALID_TAG_TYPES)
626
TDdmSimple = TElemOf(constants.DDMS_VALUES)
627
TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
628

    
629

    
630
@WithDesc("IPv4 network")
631
def _CheckCIDRNetNotation(value):
632
  """Ensure a given CIDR notation type is valid.
633

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

    
641

    
642
@WithDesc("IPv4 address")
643
def _CheckCIDRAddrNotation(value):
644
  """Ensure a given CIDR notation type is valid.
645

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

    
653

    
654
@WithDesc("IPv6 address")
655
def _CheckCIDR6AddrNotation(value):
656
  """Ensure a given CIDR notation type is valid.
657

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

    
665

    
666
@WithDesc("IPv6 network")
667
def _CheckCIDR6NetNotation(value):
668
  """Ensure a given CIDR notation type is valid.
669

670
  """
671
  try:
672
    ipaddr.IPv6Network(value)
673
  except ipaddr.AddressValueError:
674
    return False
675
  return True
676

    
677

    
678
TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation)
679
TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation)
680
TIPv4Network = TAnd(TString, _CheckCIDRNetNotation)
681
TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
682

    
683

    
684
def TObject(val_type):
685
  return TDictOf(TAny, val_type)
686

    
687

    
688
def TObjectCheck(obj, fields_types):
689
  """Helper to generate type checks for objects.
690

691
  @param obj: The object to generate type checks
692
  @param fields_types: The fields and their types as a dict
693
  @return: A ht type check function
694

695
  """
696
  assert set(obj.GetAllSlots()) == set(fields_types.keys()), \
697
    "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys()))
698
  return TStrictDict(True, True, fields_types)
699

    
700

    
701
TQueryFieldDef = \
702
    TObjectCheck(objects.QueryFieldDefinition, {
703
        "name": TNonEmptyString,
704
        "title": TNonEmptyString,
705
        "kind": TElemOf(constants.QFT_ALL),
706
        "doc": TNonEmptyString
707
    })
708

    
709
TQueryRow = \
710
    TListOf(TAnd(TIsLength(2),
711
                 TItems([TElemOf(constants.RS_ALL), TAny])))
712

    
713
TQueryResult = TListOf(TQueryRow)
714

    
715
TQueryResponse = \
716
    TObjectCheck(objects.QueryResponse, {
717
        "fields": TListOf(TQueryFieldDef),
718
        "data": TQueryResult
719
    })
720

    
721
TQueryFieldsResponse = \
722
    TObjectCheck(objects.QueryFieldsResponse, {
723
        "fields": TListOf(TQueryFieldDef)
724
    })
725

    
726
TJobIdListItem = \
727
    TAnd(TIsLength(2),
728
         TItems([Comment("success")(TBool),
729
                 Comment("Job ID if successful, error message"
730
                         " otherwise")(TOr(TString, TJobId))]))
731

    
732
TJobIdList = TListOf(TJobIdListItem)
733

    
734
TJobIdListOnly = TStrictDict(True, True, {
735
  constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList)
736
  })
737

    
738
TInstanceMultiAllocResponse = \
739
    TStrictDict(True, True, {
740
      constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList),
741
      constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString),
742
      constants.FAILED_KEY: TListOf(TNonEmptyString)
743
    })