Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 884dc063

History | View | Annotate | Download (17 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
#: The no-type (value too complex to check it in the type system)
164
NoType = object()
165

    
166

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

172
  """
173
  return True
174

    
175

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

180
  """
181
  return val is not None
182

    
183

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

188
  """
189
  return val is None
190

    
191

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

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

    
199

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

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

    
207

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

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

    
220

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

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

    
228

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

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

    
236

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

241
  """
242
  return bool(val)
243

    
244

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

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

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

    
254

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

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

    
263

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

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

    
271

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

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

    
279

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

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

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

    
289

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

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

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

    
300

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

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

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

    
310

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

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

    
318

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

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

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

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

    
330

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

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

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

    
339

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

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

    
346

    
347
# Type aliases
348

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

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

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

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

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

    
364

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

    
370

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

    
376

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

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

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

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

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

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

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

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

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

    
408

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

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

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

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

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

    
422

    
423
def TListOf(my_type):
424
  """Checks if a given value is a list with all elements of the same type.
425

426
  """
427
  desc = WithDesc("List of %s" % (Parens(my_type), ))
428
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
429

    
430

    
431
TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
432

    
433

    
434
def TTupleOf(*val_types):
435
  """Checks if a given value is a list with the proper size and its
436
     elements match the given types.
437

438
  """
439
  desc = WithDesc("Tuple of %s" % (Parens(val_types), ))
440
  return desc(TAnd(TIsLength(len(val_types)), TItems(val_types)))
441

    
442

    
443
def TSetOf(val_type):
444
  """Checks if a given value is a list with all elements of the same
445
     type and eliminates duplicated elements.
446

447
  """
448
  desc = WithDesc("Set of %s" % (Parens(val_type), ))
449
  return desc(lambda st: TListOf(val_type)(list(set(st))))
450

    
451

    
452
def TDictOf(key_type, val_type):
453
  """Checks a dict type for the type of its key/values.
454

455
  """
456
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
457
                  (Parens(key_type), Parens(val_type)))
458

    
459
  def fn(container):
460
    return (compat.all(key_type(v) for v in container.keys()) and
461
            compat.all(val_type(v) for v in container.values()))
462

    
463
  return desc(TAnd(TDict, fn))
464

    
465

    
466
def _TStrictDictCheck(require_all, exclusive, items, val):
467
  """Helper function for L{TStrictDict}.
468

469
  """
470
  notfound_fn = lambda _: not exclusive
471

    
472
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
473
    # Requires items not found in value
474
    return False
475

    
476
  return compat.all(items.get(key, notfound_fn)(value)
477
                    for (key, value) in val.items())
478

    
479

    
480
def TStrictDict(require_all, exclusive, items):
481
  """Strict dictionary check with specific keys.
482

483
  @type require_all: boolean
484
  @param require_all: Whether all keys in L{items} are required
485
  @type exclusive: boolean
486
  @param exclusive: Whether only keys listed in L{items} should be accepted
487
  @type items: dictionary
488
  @param items: Mapping from key (string) to verification function
489

490
  """
491
  descparts = ["Dictionary containing"]
492

    
493
  if exclusive:
494
    descparts.append(" none but the")
495

    
496
  if require_all:
497
    descparts.append(" required")
498

    
499
  if len(items) == 1:
500
    descparts.append(" key ")
501
  else:
502
    descparts.append(" keys ")
503

    
504
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
505
                                   for (key, value) in items.items()))
506

    
507
  desc = WithDesc("".join(descparts))
508

    
509
  return desc(TAnd(TDict,
510
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
511
                                  items)))
512

    
513

    
514
def TItems(items):
515
  """Checks individual items of a container.
516

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

521
  @type items: list
522
  @param items: List of checks
523

524
  """
525
  assert items, "Need items"
526

    
527
  text = ["Item", "item"]
528
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
529
                                  (text[int(idx > 0)], idx, Parens(check))
530
                                  for (idx, check) in enumerate(items)))
531

    
532
  return desc(lambda value: compat.all(check(i)
533
                                       for (check, i) in zip(items, value)))
534

    
535

    
536
TAllocPolicy = TElemOf(constants.VALID_ALLOC_POLICIES)
537
TCVErrorCode = TElemOf(constants.CV_ALL_ECODES_STRINGS)
538
TQueryResultCode = TElemOf(constants.RS_ALL)
539
TExportTarget = TOr(TNonEmptyString, TList)
540
TExportMode = TElemOf(constants.EXPORT_MODES)
541
TDiskIndex = TAnd(TNonNegativeInt, lambda val: val < constants.MAX_DISKS)
542
TReplaceDisksMode = TElemOf(constants.REPLACE_MODES)
543
TDiskTemplate = TElemOf(constants.DISK_TEMPLATES)
544
TNodeEvacMode = TElemOf(constants.IALLOCATOR_NEVAC_MODES)
545
TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)
546
TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES)
547

    
548

    
549
def TSetParamsMods(fn):
550
  """Generates a check for modification lists.
551

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

    
559
  # New format, supporting adding/removing disks/NICs at arbitrary indices
560
  mod_item_fn = \
561
      TAnd(TIsLength(3), TItems([
562
        TElemOf(constants.DDMS_VALUES_WITH_MODIFY),
563
        Comment("Device index, can be negative, e.g. -1 for last disk")
564
                 (TOr(TInt, TString)),
565
        fn,
566
        ]))
567

    
568
  return TOr(Comment("Recommended")(TListOf(mod_item_fn)),
569
             Comment("Deprecated")(TListOf(old_mod_item_fn)))
570

    
571

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

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

    
580
THypervisor = TElemOf(constants.HYPER_TYPES)
581
TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES)
582
TNICMode = TElemOf(constants.NIC_VALID_MODES)
583
TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES)
584
TRebootType = TElemOf(constants.REBOOT_TYPES)
585
TFileDriver = TElemOf(constants.FILE_DRIVER)
586
TOobCommand = TElemOf(constants.OOB_COMMANDS)
587
TQueryTypeOp = TElemOf(constants.QR_VIA_OP)
588

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

    
593
TDiskChanges = \
594
    TAnd(TIsLength(2),
595
         TItems([Comment("Disk index")(TNonNegativeInt),
596
                 Comment("Parameters")(TDiskParams)]))
597

    
598
TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
599

    
600

    
601
def TStorageType(val):
602
  """Builds a function that checks if a given value is a valid storage
603
  type.
604

605
  """
606
  return (val in constants.STORAGE_TYPES)
607

    
608

    
609
TTagKind = TElemOf(constants.VALID_TAG_TYPES)
610
TDdmSimple = TElemOf(constants.DDMS_VALUES)
611
TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
612

    
613

    
614
@WithDesc("IPv4 network")
615
def _CheckCIDRNetNotation(value):
616
  """Ensure a given CIDR notation type is valid.
617

618
  """
619
  try:
620
    ipaddr.IPv4Network(value)
621
  except ipaddr.AddressValueError:
622
    return False
623
  return True
624

    
625

    
626
@WithDesc("IPv4 address")
627
def _CheckCIDRAddrNotation(value):
628
  """Ensure a given CIDR notation type is valid.
629

630
  """
631
  try:
632
    ipaddr.IPv4Address(value)
633
  except ipaddr.AddressValueError:
634
    return False
635
  return True
636

    
637

    
638
@WithDesc("IPv6 address")
639
def _CheckCIDR6AddrNotation(value):
640
  """Ensure a given CIDR notation type is valid.
641

642
  """
643
  try:
644
    ipaddr.IPv6Address(value)
645
  except ipaddr.AddressValueError:
646
    return False
647
  return True
648

    
649

    
650
@WithDesc("IPv6 network")
651
def _CheckCIDR6NetNotation(value):
652
  """Ensure a given CIDR notation type is valid.
653

654
  """
655
  try:
656
    ipaddr.IPv6Network(value)
657
  except ipaddr.AddressValueError:
658
    return False
659
  return True
660

    
661

    
662
TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation)
663
TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation)
664
TIPv4Network = TAnd(TString, _CheckCIDRNetNotation)
665
TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
666

    
667

    
668
def TObject(val_type):
669
  return TDictOf(TAny, val_type)
670

    
671

    
672
def TObjectCheck(obj, fields_types):
673
  """Helper to generate type checks for objects.
674

675
  @param obj: The object to generate type checks
676
  @param fields_types: The fields and their types as a dict
677
  @return: A ht type check function
678

679
  """
680
  assert set(obj.GetAllSlots()) == set(fields_types.keys()), \
681
    "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys()))
682
  return TStrictDict(True, True, fields_types)
683

    
684

    
685
TQueryFieldDef = \
686
    TObjectCheck(objects.QueryFieldDefinition, {
687
        "name": TNonEmptyString,
688
        "title": TNonEmptyString,
689
        "kind": TElemOf(constants.QFT_ALL),
690
        "doc": TNonEmptyString
691
    })
692

    
693
TQueryRow = \
694
    TListOf(TAnd(TIsLength(2),
695
                 TItems([TElemOf(constants.RS_ALL), TAny])))
696

    
697
TQueryResult = TListOf(TQueryRow)
698

    
699
TQueryResponse = \
700
    TObjectCheck(objects.QueryResponse, {
701
        "fields": TListOf(TQueryFieldDef),
702
        "data": TQueryResult
703
    })
704

    
705
TQueryFieldsResponse = \
706
    TObjectCheck(objects.QueryFieldsResponse, {
707
        "fields": TListOf(TQueryFieldDef)
708
    })
709

    
710
TJobIdListItem = \
711
    TAnd(TIsLength(2),
712
         TItems([Comment("success")(TBool),
713
                 Comment("Job ID if successful, error message"
714
                         " otherwise")(TOr(TString, TJobId))]))
715

    
716
TJobIdList = TListOf(TJobIdListItem)
717

    
718
TJobIdListOnly = TStrictDict(True, True, {
719
  constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList)
720
  })
721

    
722
TInstanceMultiAllocResponse = \
723
    TStrictDict(True, True, {
724
      constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList),
725
      constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString),
726
      constants.FAILED_KEY: TListOf(TNonEmptyString)
727
    })