Revision a5728081

b/lib/bootstrap.py
38 38
from ganeti import constants
39 39
from ganeti import objects
40 40
from ganeti import ssconf
41
from ganeti import hypervisor
41 42

  
42 43

  
43 44
def _InitSSHSetup():
......
204 205
    raise errors.OpPrereqError("Init.d script '%s' missing or not"
205 206
                               " executable." % constants.NODE_INITD_SCRIPT)
206 207

  
207
  utils.CheckBEParams(beparams)
208
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
209
  # hvparams is a mapping of hypervisor->hvparams dict
210
  for hv_name, hv_params in hvparams.iteritems():
211
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
212
    hv_class = hypervisor.GetHypervisor(hv_name)
213
    hv_class.CheckParameterSyntax(hv_params)
208 214

  
209 215
  # set up the inter-node password and certificate
210 216
  _InitGanetiServerSetup()
b/lib/cli.py
50 50
           "ListTags", "AddTags", "RemoveTags", "TAG_SRC_OPT",
51 51
           "FormatError", "SplitNodeOption", "SubmitOrSend",
52 52
           "JobSubmittedException", "FormatTimestamp", "ParseTimespec",
53
           "ValidateBeParams", "ToStderr", "ToStdout", "UsesRPC",
53
           "ToStderr", "ToStdout", "UsesRPC",
54 54
           "GetOnlineNodes", "JobExecutor", "SYNC_OPT",
55 55
           ]
56 56

  
......
409 409
    return (value, None)
410 410

  
411 411

  
412
def ValidateBeParams(bep):
413
  """Parse and check the given beparams.
414

  
415
  The function will update in-place the given dictionary.
416

  
417
  @type bep: dict
418
  @param bep: input beparams
419
  @raise errors.ParameterError: if the input values are not OK
420
  @raise errors.UnitParseError: if the input values are not OK
421

  
422
  """
423
  if constants.BE_MEMORY in bep:
424
    bep[constants.BE_MEMORY] = utils.ParseUnit(bep[constants.BE_MEMORY])
425

  
426
  if constants.BE_VCPUS in bep:
427
    try:
428
      bep[constants.BE_VCPUS] = int(bep[constants.BE_VCPUS])
429
    except ValueError:
430
      raise errors.ParameterError("Invalid number of VCPUs")
431

  
432

  
433 412
def UsesRPC(fn):
434 413
  def wrapper(*args, **kwargs):
435 414
    rpc.Init()
......
695 674
  elif isinstance(err, errors.JobQueueFull):
696 675
    obuf.write("Failure: the job queue is full and doesn't accept new"
697 676
               " job submissions until old jobs are archived\n")
677
  elif isinstance(err, errors.TypeEnforcementError):
678
    obuf.write("Parameter Error: %s" % msg)
698 679
  elif isinstance(err, errors.GenericError):
699 680
    obuf.write("Unhandled Ganeti error: %s" % msg)
700 681
  elif isinstance(err, luxi.NoMasterError):
b/lib/cmdlib.py
1409 1409
    if the given volume group is valid.
1410 1410

  
1411 1411
    """
1412
    # FIXME: This only works because there is only one parameter that can be
1413
    # changed or removed.
1414 1412
    if self.op.vg_name is not None and not self.op.vg_name:
1415 1413
      instances = self.cfg.GetAllInstancesInfo().values()
1416 1414
      for inst in instances:
......
1439 1437
    self.cluster = cluster = self.cfg.GetClusterInfo()
1440 1438
    # validate beparams changes
1441 1439
    if self.op.beparams:
1442
      utils.CheckBEParams(self.op.beparams)
1440
      utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
1443 1441
      self.new_beparams = cluster.FillDict(
1444 1442
        cluster.beparams[constants.BEGR_DEFAULT], self.op.beparams)
1445 1443

  
......
1467 1465
             hv_name in self.op.enabled_hypervisors)):
1468 1466
          # either this is a new hypervisor, or its parameters have changed
1469 1467
          hv_class = hypervisor.GetHypervisor(hv_name)
1468
          utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
1470 1469
          hv_class.CheckParameterSyntax(hv_params)
1471 1470
          _CheckHVParams(self, node_list, hv_name, hv_params)
1472 1471

  
......
4227 4226
                                  ",".join(enabled_hvs)))
4228 4227

  
4229 4228
    # check hypervisor parameter syntax (locally)
4230

  
4229
    utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
4231 4230
    filled_hvp = cluster.FillDict(cluster.hvparams[self.op.hypervisor],
4232 4231
                                  self.op.hvparams)
4233 4232
    hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
4234 4233
    hv_type.CheckParameterSyntax(filled_hvp)
4235 4234

  
4236 4235
    # fill and remember the beparams dict
4237
    utils.CheckBEParams(self.op.beparams)
4236
    utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
4238 4237
    self.be_full = cluster.FillDict(cluster.beparams[constants.BEGR_DEFAULT],
4239 4238
                                    self.op.beparams)
4240 4239

  
......
5608 5607
            self.op.hvparams or self.op.beparams):
5609 5608
      raise errors.OpPrereqError("No changes submitted")
5610 5609

  
5611
    utils.CheckBEParams(self.op.beparams)
5612

  
5613 5610
    # Disk validation
5614 5611
    disk_addremove = 0
5615 5612
    for disk_op, disk_dict in self.op.disks:
......
5731 5728
            del i_hvdict[key]
5732 5729
          except KeyError:
5733 5730
            pass
5734
        elif val == constants.VALUE_NONE:
5735
          i_hvdict[key] = None
5736 5731
        else:
5737 5732
          i_hvdict[key] = val
5738 5733
      cluster = self.cfg.GetClusterInfo()
5734
      utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
5739 5735
      hv_new = cluster.FillDict(cluster.hvparams[instance.hypervisor],
5740 5736
                                i_hvdict)
5741 5737
      # local check
......
5759 5755
        else:
5760 5756
          i_bedict[key] = val
5761 5757
      cluster = self.cfg.GetClusterInfo()
5758
      utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
5762 5759
      be_new = cluster.FillDict(cluster.beparams[constants.BEGR_DEFAULT],
5763 5760
                                i_bedict)
5764 5761
      self.be_new = be_new # the new actual values
b/lib/constants.py
277 277
                          INSTANCE_REBOOT_HARD,
278 278
                          INSTANCE_REBOOT_FULL])
279 279

  
280
VTYPE_STRING = 'string'
281
VTYPE_BOOL = 'bool'
282
VTYPE_SIZE = 'size' # size, in MiBs
283
VTYPE_INT = 'int'
284
ENFORCEABLE_TYPES = frozenset([
285
                      VTYPE_STRING,
286
                      VTYPE_BOOL,
287
                      VTYPE_SIZE,
288
                      VTYPE_INT,
289
                      ])
290

  
280 291
# HV parameter names (global namespace)
281 292
HV_BOOT_ORDER = "boot_order"
282 293
HV_CDROM_IMAGE_PATH = "cdrom_image_path"
......
294 305
HV_SERIAL_CONSOLE = "serial_console"
295 306
HV_USB_MOUSE = "usb_mouse"
296 307

  
297
HVS_PARAMETERS = frozenset([
298
  HV_BOOT_ORDER,
299
  HV_CDROM_IMAGE_PATH,
300
  HV_NIC_TYPE,
301
  HV_DISK_TYPE,
302
  HV_VNC_BIND_ADDRESS,
303
  HV_VNC_TLS,
304
  HV_VNC_X509,
305
  HV_VNC_X509_VERIFY,
306
  HV_ACPI,
307
  HV_PAE,
308
  HV_KERNEL_PATH,
309
  HV_INITRD_PATH,
310
  HV_ROOT_PATH,
311
  HV_SERIAL_CONSOLE,
312
  HV_USB_MOUSE,
313
  ])
308
HVS_PARAMETER_TYPES = {
309
  HV_BOOT_ORDER: VTYPE_STRING,
310
  HV_CDROM_IMAGE_PATH: VTYPE_STRING,
311
  HV_NIC_TYPE: VTYPE_STRING,
312
  HV_DISK_TYPE: VTYPE_STRING,
313
  HV_VNC_BIND_ADDRESS: VTYPE_STRING,
314
  HV_VNC_TLS: VTYPE_BOOL,
315
  HV_VNC_X509: VTYPE_STRING,
316
  HV_VNC_X509_VERIFY: VTYPE_BOOL,
317
  HV_ACPI: VTYPE_BOOL,
318
  HV_PAE: VTYPE_BOOL,
319
  HV_KERNEL_PATH: VTYPE_STRING,
320
  HV_INITRD_PATH: VTYPE_STRING,
321
  HV_ROOT_PATH: VTYPE_STRING,
322
  HV_SERIAL_CONSOLE: VTYPE_BOOL,
323
  HV_USB_MOUSE: VTYPE_STRING,
324
  }
325

  
326
HVS_PARAMETERS = frozenset(HVS_PARAMETER_TYPES.keys())
314 327

  
315 328
# BE parameter names
316 329
BE_MEMORY = "memory"
317 330
BE_VCPUS = "vcpus"
318 331
BE_AUTO_BALANCE = "auto_balance"
319 332

  
320
BES_PARAMETERS = frozenset([
321
  BE_MEMORY,
322
  BE_VCPUS,
323
  BE_AUTO_BALANCE,
324
  ])
333
BES_PARAMETER_TYPES = {
334
    BE_MEMORY: VTYPE_SIZE,
335
    BE_VCPUS: VTYPE_INT,
336
    BE_AUTO_BALANCE: VTYPE_BOOL,
337
    }
338

  
339
BES_PARAMETERS = frozenset(BES_PARAMETER_TYPES.keys())
325 340

  
326 341
# BE GROUP
327 342
BEGR_DEFAULT = "default"
......
457 472
HVC_DEFAULTS = {
458 473
  HT_XEN_PVM: {
459 474
    HV_KERNEL_PATH: "/boot/vmlinuz-2.6-xenU",
460
    HV_INITRD_PATH: None,
475
    HV_INITRD_PATH: '',
461 476
    HV_ROOT_PATH: '/dev/sda',
462 477
    },
463 478
  HT_XEN_HVM: {
464 479
    HV_BOOT_ORDER: "cd",
465
    HV_CDROM_IMAGE_PATH: None,
480
    HV_CDROM_IMAGE_PATH: '',
466 481
    HV_NIC_TYPE: HT_NIC_RTL8139,
467 482
    HV_DISK_TYPE: HT_DISK_PARAVIRTUAL,
468 483
    HV_VNC_BIND_ADDRESS: '0.0.0.0',
......
471 486
    },
472 487
  HT_KVM: {
473 488
    HV_KERNEL_PATH: "/boot/vmlinuz-2.6-kvmU",
474
    HV_INITRD_PATH: None,
489
    HV_INITRD_PATH: '',
475 490
    HV_ROOT_PATH: '/dev/vda',
476 491
    HV_ACPI: True,
477 492
    HV_SERIAL_CONSOLE: True,
478
    HV_VNC_BIND_ADDRESS: None,
493
    HV_VNC_BIND_ADDRESS: '',
479 494
    HV_VNC_TLS: False,
480 495
    HV_VNC_X509: '',
481 496
    HV_VNC_X509_VERIFY: False,
482
    HV_CDROM_IMAGE_PATH: None,
497
    HV_CDROM_IMAGE_PATH: '',
483 498
    HV_BOOT_ORDER: "disk",
484 499
    HV_NIC_TYPE: HT_NIC_PARAVIRTUAL,
485 500
    HV_DISK_TYPE: HT_DISK_PARAVIRTUAL,
486
    HV_USB_MOUSE: None,
501
    HV_USB_MOUSE: '',
487 502
    },
488 503
  HT_FAKE: {
489 504
    },
b/lib/errors.py
198 198

  
199 199
  """
200 200

  
201
class TypeEnforcementError(GenericError):
202
  """Unable to enforce data type.
203

  
204
  """
201 205

  
202 206
class SshKeyError(GenericError):
203 207
  """Invalid SSH key.
b/lib/utils.py
387 387
    logging.warning('%s missing keys %s', logname, ', '.join(missing))
388 388

  
389 389

  
390
def ForceDictType(target, key_types, allowed_values=None):
391
  """Force the values of a dict to have certain types.
392

  
393
  @type target: dict
394
  @param target: the dict to update
395
  @type key_types: dict
396
  @param key_types: dict mapping target dict keys to types
397
                    in constants.ENFORCEABLE_TYPES
398
  @type allowed_values: list
399
  @keyword allowed_values: list of specially allowed values
400

  
401
  """
402
  if allowed_values is None:
403
    allowed_values = []
404

  
405
  for key in target:
406
    if key not in key_types:
407
      msg = "Unknown key '%s'" % key
408
      raise errors.TypeEnforcementError(msg)
409

  
410
    if target[key] in allowed_values:
411
      continue
412

  
413
    type = key_types[key]
414
    if type not in constants.ENFORCEABLE_TYPES:
415
      msg = "'%s' has non-enforceable type %s" % (key, type)
416
      raise errors.ProgrammerError(msg)
417

  
418
    if type == constants.VTYPE_STRING:
419
      if not isinstance(target[key], basestring):
420
        if isinstance(target[key], bool) and not target[key]:
421
          target[key] = ''
422
        else:
423
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
424
          raise errors.TypeEnforcementError(msg)
425
    elif type == constants.VTYPE_BOOL:
426
      if isinstance(target[key], basestring) and target[key]:
427
        if target[key].lower() == constants.VALUE_FALSE:
428
          target[key] = False
429
        elif target[key].lower() == constants.VALUE_TRUE:
430
          target[key] = True
431
        else:
432
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
433
          raise errors.TypeEnforcementError(msg)
434
      elif target[key]:
435
        target[key] = True
436
      else:
437
        target[key] = False
438
    elif type == constants.VTYPE_SIZE:
439
      try:
440
        target[key] = ParseUnit(target[key])
441
      except errors.UnitParseError, err:
442
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
443
              (key, target[key], err)
444
        raise errors.TypeEnforcementError(msg)
445
    elif type == constants.VTYPE_INT:
446
      try:
447
        target[key] = int(target[key])
448
      except (ValueError, TypeError):
449
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
450
        raise errors.TypeEnforcementError(msg)
451

  
452

  
390 453
def IsProcessAlive(pid):
391 454
  """Check if a given pid exists on the system.
392 455

  
......
558 621
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
559 622

  
560 623

  
561
def CheckBEParams(beparams):
562
  """Checks whether the user-supplied be-params are valid,
563
  and converts them from string format where appropriate.
564

  
565
  @type beparams: dict
566
  @param beparams: new params dict
567

  
568
  """
569
  if beparams:
570
    for item in beparams:
571
      if item not in constants.BES_PARAMETERS:
572
        raise errors.OpPrereqError("Unknown backend parameter %s" % item)
573
      if item in (constants.BE_MEMORY, constants.BE_VCPUS):
574
        val = beparams[item]
575
        if val != constants.VALUE_DEFAULT:
576
          try:
577
            val = int(val)
578
          except ValueError, err:
579
            raise errors.OpPrereqError("Invalid %s size: %s" % (item, err))
580
          beparams[item] = val
581
      if item in (constants.BE_AUTO_BALANCE):
582
        val = beparams[item]
583
        if not isinstance(val, bool):
584
          if val == constants.VALUE_TRUE:
585
            beparams[item] = True
586
          elif val == constants.VALUE_FALSE:
587
            beparams[item] = False
588
          else:
589
            raise errors.OpPrereqError("Invalid %s value: %s" % (item, val))
590

  
591

  
592 624
def NiceSort(name_list):
593 625
  """Sort a list of strings based on digit and non-digit groupings.
594 626

  
b/scripts/gnt-backup
136 136
                                   (didx, err))
137 137
      disks[didx] = ddict
138 138

  
139
  ValidateBeParams(opts.beparams)
139
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
140
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES)
140 141

  
141 142
  op = opcodes.OpCreateInstance(instance_name=instance,
142 143
                                disk_template=opts.disk_template,
b/scripts/gnt-cluster
88 88
  for parameter in constants.BES_PARAMETERS:
89 89
    if parameter not in beparams:
90 90
      beparams[parameter] = constants.BEC_DEFAULTS[parameter]
91

  
92
  # type wrangling
93
  try:
94
    beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
95
  except ValueError:
96
    ToStderr("%s must be an integer", constants.BE_VCPUS)
97
    return 1
98

  
99
  if not isinstance(beparams[constants.BE_MEMORY], int):
100
    beparams[constants.BE_MEMORY] = utils.ParseUnit(
101
        beparams[constants.BE_MEMORY])
91
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
102 92

  
103 93
  # prepare hvparams dict
104 94
  for hv in constants.HYPER_TYPES:
......
107 97
    for parameter in constants.HVC_DEFAULTS[hv]:
108 98
      if parameter not in hvparams[hv]:
109 99
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
100
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
110 101

  
111 102
  for hv in hvlist:
112 103
    if hv not in constants.HYPER_TYPES:
......
486 477
  if hvparams:
487 478
    # a list of (name, dict) we can pass directly to dict()
488 479
    hvparams = dict(opts.hvparams)
480
  for hv, hv_params in hvparams.iteritems():
481
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
489 482

  
490 483
  beparams = opts.beparams
484
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
491 485

  
492 486
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
493 487
                                  enabled_hypervisors=hvlist,
b/scripts/gnt-instance
355 355
                                   (didx, err))
356 356
      disks[didx] = ddict
357 357

  
358
  ValidateBeParams(opts.beparams)
359

  
360
##  kernel_path = _TransformPath(opts.kernel_path)
361
##  initrd_path = _TransformPath(opts.initrd_path)
362

  
363
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
364
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
365

  
366
##  if ((opts.hvm_cdrom_image_path is not None) and
367
##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
368
##    hvm_cdrom_image_path = None
369
##  else:
370
##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
358
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
359
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES)
371 360

  
372 361
  op = opcodes.OpCreateInstance(instance_name=instance,
373 362
                                disks=disks,
......
492 481

  
493 482
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
494 483

  
484
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
485
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
486

  
495 487
    op = opcodes.OpCreateInstance(instance_name=name,
496 488
                                  disks=disks,
497 489
                                  disk_template=specs['template'],
......
1187 1179
    if isinstance(opts.beparams[param], basestring):
1188 1180
      if opts.beparams[param].lower() == "default":
1189 1181
        opts.beparams[param] = constants.VALUE_DEFAULT
1190
      elif opts.beparams[param].lower() == "none":
1191
        opts.beparams[param] = constants.VALUE_NONE
1192
      elif param == constants.BE_MEMORY:
1193
        opts.beparams[constants.BE_MEMORY] = \
1194
          utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1182

  
1183
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1184
                      allowed_values=[constants.VALUE_DEFAULT])
1195 1185

  
1196 1186
  for param in opts.hypervisor:
1197 1187
    if isinstance(opts.hypervisor[param], basestring):
1198 1188
      if opts.hypervisor[param].lower() == "default":
1199 1189
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1200
      elif opts.hypervisor[param].lower() == "none":
1201
        opts.hypervisor[param] = constants.VALUE_NONE
1190

  
1191
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1192
                      allowed_values=[constants.VALUE_DEFAULT])
1202 1193

  
1203 1194
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1204 1195
    try:
b/test/ganeti.utils_unittest.py
38 38
import testutils
39 39
from ganeti import constants
40 40
from ganeti import utils
41
from ganeti import errors
41 42
from ganeti.utils import IsProcessAlive, RunCmd, \
42 43
     RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
43 44
     ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
44 45
     ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \
45 46
     SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress, \
46
     TailFile
47
     TailFile, ForceDictType
47 48

  
48 49
from ganeti.errors import LockError, UnitParseError, GenericError, \
49 50
     ProgrammerError
......
921 922
    self.failIf(f.NonMatching(["b12", "c"]))
922 923
    self.failUnless(f.NonMatching(["a", "1"]))
923 924

  
925
class TestForceDictType(unittest.TestCase):
926
  """Test case for ForceDictType"""
927

  
928
  def setUp(self):
929
    self.key_types = {
930
      'a': constants.VTYPE_INT,
931
      'b': constants.VTYPE_BOOL,
932
      'c': constants.VTYPE_STRING,
933
      'd': constants.VTYPE_SIZE,
934
      }
935

  
936
  def _fdt(self, dict, allowed_values=None):
937
    if allowed_values is None:
938
      ForceDictType(dict, self.key_types)
939
    else:
940
      ForceDictType(dict, self.key_types, allowed_values=allowed_values)
941

  
942
    return dict
943

  
944
  def testSimpleDict(self):
945
    self.assertEqual(self._fdt({}), {})
946
    self.assertEqual(self._fdt({'a': 1}), {'a': 1})
947
    self.assertEqual(self._fdt({'a': '1'}), {'a': 1})
948
    self.assertEqual(self._fdt({'a': 1, 'b': 1}), {'a':1, 'b': True})
949
    self.assertEqual(self._fdt({'b': 1, 'c': 'foo'}), {'b': True, 'c': 'foo'})
950
    self.assertEqual(self._fdt({'b': 1, 'c': False}), {'b': True, 'c': ''})
951
    self.assertEqual(self._fdt({'b': 'false'}), {'b': False})
952
    self.assertEqual(self._fdt({'b': 'False'}), {'b': False})
953
    self.assertEqual(self._fdt({'b': 'true'}), {'b': True})
954
    self.assertEqual(self._fdt({'b': 'True'}), {'b': True})
955
    self.assertEqual(self._fdt({'d': '4'}), {'d': 4})
956
    self.assertEqual(self._fdt({'d': '4M'}), {'d': 4})
957

  
958
  def testErrors(self):
959
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'a': 'astring'})
960
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'c': True})
961
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'d': 'astring'})
962
    self.assertRaises(errors.TypeEnforcementError, self._fdt, {'d': '4 L'})
963

  
924 964

  
925 965
if __name__ == '__main__':
926 966
  unittest.main()

Also available in: Unified diff