Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 653bc0f1

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
TImportExportCompression = TElemOf(constants.IEC_ALL)
544

    
545

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

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

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

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

    
568

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

    
573
TIDiskParams = \
574
    Comment("Disk parameters")(TDictOf(TNonEmptyString,
575
                                       TOr(TNonEmptyString, TInt)))
576

    
577
THypervisor = TElemOf(constants.HYPER_TYPES)
578
TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES)
579
TNICMode = TElemOf(constants.NIC_VALID_MODES)
580
TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES)
581
TRebootType = TElemOf(constants.REBOOT_TYPES)
582
TFileDriver = TElemOf(constants.FILE_DRIVER)
583
TOobCommand = TElemOf(constants.OOB_COMMANDS)
584
# FIXME: adjust this after all queries are in haskell
585
TQueryTypeOp = TElemOf(set(constants.QR_VIA_OP)
586
                       .union(set(constants.QR_VIA_LUXI)))
587

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

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

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

    
599

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

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

    
607

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

    
612

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

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

    
624

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

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

    
636

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

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

    
648

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

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

    
660

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

    
666

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

    
670

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

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

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

    
683

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

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

    
696
TQueryResult = TListOf(TQueryRow)
697

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

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

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

    
715
TJobIdList = TListOf(TJobIdListItem)
716

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

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