Revision 66222813

b/Makefile.am
1240 1240
python_tests = \
1241 1241
	doc/examples/rapi_testutils.py \
1242 1242
	test/py/cmdlib/cluster_unittest.py \
1243
	test/py/cmdlib/group_unittest.py \
1244
	test/py/cmdlib/test_unittest.py \
1245 1243
	test/py/cmdlib/cmdlib_unittest.py \
1244
	test/py/cmdlib/group_unittest.py \
1245
	test/py/cmdlib/instance_unittest.py \
1246 1246
	test/py/cmdlib/instance_storage_unittest.py \
1247
	test/py/cmdlib/test_unittest.py \
1247 1248
	test/py/cfgupgrade_unittest.py \
1248 1249
	test/py/docs_unittest.py \
1249 1250
	test/py/ganeti.asyncnotifier_unittest.py \
b/lib/cmdlib/instance.py
444 444
    self._CheckVLANArguments()
445 445

  
446 446
    self._CheckDiskArguments()
447
    assert self.op.disk_template is not None
447 448

  
448 449
    # instance name verification
449 450
    if self.op.name_check:
......
454 455
    else:
455 456
      self.check_ip = None
456 457

  
457
    # file storage checks
458
    if (self.op.file_driver and
459
        not self.op.file_driver in constants.FILE_DRIVER):
460
      raise errors.OpPrereqError("Invalid file driver name '%s'" %
461
                                 self.op.file_driver, errors.ECODE_INVAL)
462

  
463 458
    ### Node/iallocator related checks
464 459
    CheckIAllocatorOrNode(self, "iallocator", "pnode")
465 460

  
......
475 470

  
476 471
    _CheckOpportunisticLocking(self.op)
477 472

  
478
    self._cds = GetClusterDomainSecret()
479

  
480 473
    if self.op.mode == constants.INSTANCE_IMPORT:
481 474
      # On import force_variant must be True, because if we forced it at
482 475
      # initial install, our only chance when importing it back is that it
......
494 487
        raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
495 488
                                   " installation" % self.op.os_type,
496 489
                                   errors.ECODE_STATE)
497
      if self.op.disk_template is None:
498
        raise errors.OpPrereqError("No disk template specified",
499
                                   errors.ECODE_INVAL)
500

  
501 490
    elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
491
      self._cds = GetClusterDomainSecret()
492

  
502 493
      # Check handshake to ensure both clusters have the same domain secret
503 494
      src_handshake = self.op.source_handshake
504 495
      if not src_handshake:
......
619 610
    else:
620 611
      node_name_whitelist = None
621 612

  
622
    #TODO Export network to iallocator so that it chooses a pnode
623
    #     in a nodegroup that has the desired network connected to
624 613
    req = _CreateInstanceAllocRequest(self.op, self.disks,
625 614
                                      self.nics, self.be_full,
626 615
                                      node_name_whitelist)
......
1020 1009
        netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.uuid)
1021 1010
        if netparams is None:
1022 1011
          raise errors.OpPrereqError("No netparams found for network"
1023
                                     " %s. Propably not connected to"
1012
                                     " %s. Probably not connected to"
1024 1013
                                     " node's %s nodegroup" %
1025 1014
                                     (nobj.name, self.pnode.name),
1026 1015
                                     errors.ECODE_INVAL)
b/test/py/cmdlib/cmdlib_unittest.py
31 31
from ganeti import mcpu
32 32
from ganeti import cmdlib
33 33
from ganeti.cmdlib import cluster
34
from ganeti.cmdlib import group
35 34
from ganeti.cmdlib import instance
36 35
from ganeti.cmdlib import instance_storage
37 36
from ganeti.cmdlib import instance_utils
......
627 626
    self.assertEqual(ret, [])
628 627

  
629 628

  
630
class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
631
  def test(self):
632
    ispec = {
633
      constants.ISPEC_MEM_SIZE: 2048,
634
      constants.ISPEC_CPU_COUNT: 2,
635
      constants.ISPEC_DISK_COUNT: 1,
636
      constants.ISPEC_DISK_SIZE: [512],
637
      constants.ISPEC_NIC_COUNT: 0,
638
      constants.ISPEC_SPINDLE_USE: 1,
639
      }
640
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
641
                                            constants.DT_PLAIN)
642
    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
643
                                                        constants.DT_PLAIN,
644
                                                        _compute_fn=stub)
645
    self.assertEqual(ret, [])
646

  
647

  
648 629
class _CallRecorder:
649 630
  def __init__(self, return_value=None):
650 631
    self.called = False
......
678 659
    self.assertEqual(ret, [])
679 660

  
680 661

  
681
class _FakeConfigForTargetNodeIPolicy:
682
  def __init__(self, node_info=NotImplemented):
683
    self._node_info = node_info
684

  
685
  def GetNodeInfo(self, _):
686
    return self._node_info
687

  
688

  
689
class TestCheckTargetNodeIPolicy(unittest.TestCase):
690
  def setUp(self):
691
    self.instance = objects.Instance(primary_node="blubb")
692
    self.target_node = objects.Node(group="bar")
693
    node_info = objects.Node(group="foo")
694
    fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
695
    self.lu = _FakeLU(cfg=fake_cfg)
696

  
697
  def testNoViolation(self):
698
    compute_recoder = _CallRecorder(return_value=[])
699
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
700
                                    self.target_node, NotImplemented,
701
                                    _compute_fn=compute_recoder)
702
    self.assertTrue(compute_recoder.called)
703
    self.assertEqual(self.lu.warning_log, [])
704

  
705
  def testNoIgnore(self):
706
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
707
    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
708
                      self.lu, NotImplemented, self.instance,
709
                      self.target_node, NotImplemented,
710
                      _compute_fn=compute_recoder)
711
    self.assertTrue(compute_recoder.called)
712
    self.assertEqual(self.lu.warning_log, [])
713

  
714
  def testIgnoreViolation(self):
715
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
716
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
717
                                     self.target_node, NotImplemented,
718
                                     ignore=True, _compute_fn=compute_recoder)
719
    self.assertTrue(compute_recoder.called)
720
    msg = ("Instance does not meet target node group's (bar) instance policy:"
721
           " mem_size not in range")
722
    self.assertEqual(self.lu.warning_log, [(msg, ())])
723

  
724

  
725
class TestApplyContainerMods(unittest.TestCase):
726
  def testEmptyContainer(self):
727
    container = []
728
    chgdesc = []
729
    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
730
                                None)
731
    self.assertEqual(container, [])
732
    self.assertEqual(chgdesc, [])
733

  
734
  def testAdd(self):
735
    container = []
736
    chgdesc = []
737
    mods = instance._PrepareContainerMods([
738
      (constants.DDM_ADD, -1, "Hello"),
739
      (constants.DDM_ADD, -1, "World"),
740
      (constants.DDM_ADD, 0, "Start"),
741
      (constants.DDM_ADD, -1, "End"),
742
      ], None)
743
    instance._ApplyContainerMods("test", container, chgdesc, mods,
744
                                None, None, None)
745
    self.assertEqual(container, ["Start", "Hello", "World", "End"])
746
    self.assertEqual(chgdesc, [])
747

  
748
    mods = instance._PrepareContainerMods([
749
      (constants.DDM_ADD, 0, "zero"),
750
      (constants.DDM_ADD, 3, "Added"),
751
      (constants.DDM_ADD, 5, "four"),
752
      (constants.DDM_ADD, 7, "xyz"),
753
      ], None)
754
    instance._ApplyContainerMods("test", container, chgdesc, mods,
755
                                None, None, None)
756
    self.assertEqual(container,
757
                     ["zero", "Start", "Hello", "Added", "World", "four",
758
                      "End", "xyz"])
759
    self.assertEqual(chgdesc, [])
760

  
761
    for idx in [-2, len(container) + 1]:
762
      mods = instance._PrepareContainerMods([
763
        (constants.DDM_ADD, idx, "error"),
764
        ], None)
765
      self.assertRaises(IndexError, instance._ApplyContainerMods,
766
                        "test", container, None, mods, None, None, None)
767

  
768
  def testRemoveError(self):
769
    for idx in [0, 1, 2, 100, -1, -4]:
770
      mods = instance._PrepareContainerMods([
771
        (constants.DDM_REMOVE, idx, None),
772
        ], None)
773
      self.assertRaises(IndexError, instance._ApplyContainerMods,
774
                        "test", [], None, mods, None, None, None)
775

  
776
    mods = instance._PrepareContainerMods([
777
      (constants.DDM_REMOVE, 0, object()),
778
      ], None)
779
    self.assertRaises(AssertionError, instance._ApplyContainerMods,
780
                      "test", [""], None, mods, None, None, None)
781

  
782
  def testAddError(self):
783
    for idx in range(-100, -1) + [100]:
784
      mods = instance._PrepareContainerMods([
785
        (constants.DDM_ADD, idx, None),
786
        ], None)
787
      self.assertRaises(IndexError, instance._ApplyContainerMods,
788
                        "test", [], None, mods, None, None, None)
789

  
790
  def testRemove(self):
791
    container = ["item 1", "item 2"]
792
    mods = instance._PrepareContainerMods([
793
      (constants.DDM_ADD, -1, "aaa"),
794
      (constants.DDM_REMOVE, -1, None),
795
      (constants.DDM_ADD, -1, "bbb"),
796
      ], None)
797
    chgdesc = []
798
    instance._ApplyContainerMods("test", container, chgdesc, mods,
799
                                None, None, None)
800
    self.assertEqual(container, ["item 1", "item 2", "bbb"])
801
    self.assertEqual(chgdesc, [
802
      ("test/2", "remove"),
803
      ])
804

  
805
  def testModify(self):
806
    container = ["item 1", "item 2"]
807
    mods = instance._PrepareContainerMods([
808
      (constants.DDM_MODIFY, -1, "a"),
809
      (constants.DDM_MODIFY, 0, "b"),
810
      (constants.DDM_MODIFY, 1, "c"),
811
      ], None)
812
    chgdesc = []
813
    instance._ApplyContainerMods("test", container, chgdesc, mods,
814
                                None, None, None)
815
    self.assertEqual(container, ["item 1", "item 2"])
816
    self.assertEqual(chgdesc, [])
817

  
818
    for idx in [-2, len(container) + 1]:
819
      mods = instance._PrepareContainerMods([
820
        (constants.DDM_MODIFY, idx, "error"),
821
        ], None)
822
      self.assertRaises(IndexError, instance._ApplyContainerMods,
823
                        "test", container, None, mods, None, None, None)
824

  
825
  class _PrivateData:
826
    def __init__(self):
827
      self.data = None
828

  
829
  @staticmethod
830
  def _CreateTestFn(idx, params, private):
831
    private.data = ("add", idx, params)
832
    return ((100 * idx, params), [
833
      ("test/%s" % idx, hex(idx)),
834
      ])
835

  
836
  @staticmethod
837
  def _ModifyTestFn(idx, item, params, private):
838
    private.data = ("modify", idx, params)
839
    return [
840
      ("test/%s" % idx, "modify %s" % params),
841
      ]
842

  
843
  @staticmethod
844
  def _RemoveTestFn(idx, item, private):
845
    private.data = ("remove", idx, item)
846

  
847
  def testAddWithCreateFunction(self):
848
    container = []
849
    chgdesc = []
850
    mods = instance._PrepareContainerMods([
851
      (constants.DDM_ADD, -1, "Hello"),
852
      (constants.DDM_ADD, -1, "World"),
853
      (constants.DDM_ADD, 0, "Start"),
854
      (constants.DDM_ADD, -1, "End"),
855
      (constants.DDM_REMOVE, 2, None),
856
      (constants.DDM_MODIFY, -1, "foobar"),
857
      (constants.DDM_REMOVE, 2, None),
858
      (constants.DDM_ADD, 1, "More"),
859
      ], self._PrivateData)
860
    instance._ApplyContainerMods("test", container, chgdesc, mods,
861
                                self._CreateTestFn, self._ModifyTestFn,
862
                                self._RemoveTestFn)
863
    self.assertEqual(container, [
864
      (000, "Start"),
865
      (100, "More"),
866
      (000, "Hello"),
867
      ])
868
    self.assertEqual(chgdesc, [
869
      ("test/0", "0x0"),
870
      ("test/1", "0x1"),
871
      ("test/0", "0x0"),
872
      ("test/3", "0x3"),
873
      ("test/2", "remove"),
874
      ("test/2", "modify foobar"),
875
      ("test/2", "remove"),
876
      ("test/1", "0x1")
877
      ])
878
    self.assertTrue(compat.all(op == private.data[0]
879
                               for (op, _, _, private) in mods))
880
    self.assertEqual([private.data for (op, _, _, private) in mods], [
881
      ("add", 0, "Hello"),
882
      ("add", 1, "World"),
883
      ("add", 0, "Start"),
884
      ("add", 3, "End"),
885
      ("remove", 2, (100, "World")),
886
      ("modify", 2, "foobar"),
887
      ("remove", 2, (300, "End")),
888
      ("add", 1, "More"),
889
      ])
890

  
891

  
892
class _FakeConfigForGenDiskTemplate:
893
  def __init__(self, enabled_disk_templates):
894
    self._unique_id = itertools.count()
895
    self._drbd_minor = itertools.count(20)
896
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
897
    self._secret = itertools.count()
898
    self._enabled_disk_templates = enabled_disk_templates
899

  
900
  def GetVGName(self):
901
    return "testvg"
902

  
903
  def GenerateUniqueID(self, ec_id):
904
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
905

  
906
  def AllocateDRBDMinor(self, nodes, instance):
907
    return [self._drbd_minor.next()
908
            for _ in nodes]
909

  
910
  def AllocatePort(self):
911
    return self._port.next()
912

  
913
  def GenerateDRBDSecret(self, ec_id):
914
    return "ec%s-secret%s" % (ec_id, self._secret.next())
915

  
916
  def GetInstanceInfo(self, _):
917
    return "foobar"
918

  
919
  def GetClusterInfo(self):
920
    cluster = objects.Cluster()
921
    cluster.enabled_disk_templates = self._enabled_disk_templates
922
    return cluster
923

  
924

  
925
class _FakeProcForGenDiskTemplate:
926
  def GetECId(self):
927
    return 0
928

  
929

  
930
class TestGenerateDiskTemplate(unittest.TestCase):
931

  
932
  def _SetUpLUWithTemplates(self, enabled_disk_templates):
933
    self._enabled_disk_templates = enabled_disk_templates
934
    cfg = _FakeConfigForGenDiskTemplate(self._enabled_disk_templates)
935
    proc = _FakeProcForGenDiskTemplate()
936

  
937
    self.lu = _FakeLU(cfg=cfg, proc=proc)
938

  
939
  def setUp(self):
940
    nodegroup = objects.NodeGroup(name="ng")
941
    nodegroup.UpgradeConfig()
942

  
943
    self._enabled_disk_templates = list(constants.DISK_TEMPLATES)
944
    self._SetUpLUWithTemplates(self._enabled_disk_templates)
945
    self.nodegroup = nodegroup
946

  
947
  @staticmethod
948
  def GetDiskParams():
949
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
950

  
951
  def testWrongDiskTemplate(self):
952
    gdt = instance.GenerateDiskTemplate
953
    disk_template = "##unknown##"
954

  
955
    assert disk_template not in constants.DISK_TEMPLATES
956

  
957
    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
958
                      "inst26831.example.com", "node30113.example.com", [], [],
959
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
960
                      self.GetDiskParams())
961

  
962
  def testDiskless(self):
963
    gdt = instance.GenerateDiskTemplate
964

  
965
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
966
                 "node30113.example.com", [], [],
967
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
968
                 self.GetDiskParams())
969
    self.assertEqual(result, [])
970

  
971
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
972
                       file_storage_dir=NotImplemented,
973
                       file_driver=NotImplemented):
974
    gdt = instance.GenerateDiskTemplate
975

  
976
    map(lambda params: utils.ForceDictType(params,
977
                                           constants.IDISK_PARAMS_TYPES),
978
        disk_info)
979

  
980
    # Check if non-empty list of secondaries is rejected
981
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
982
                      template, "inst25088.example.com",
983
                      "node185.example.com", ["node323.example.com"], [],
984
                      NotImplemented, NotImplemented, base_index,
985
                      self.lu.LogInfo, self.GetDiskParams())
986

  
987
    result = gdt(self.lu, template, "inst21662.example.com",
988
                 "node21741.example.com", [],
989
                 disk_info, file_storage_dir, file_driver, base_index,
990
                 self.lu.LogInfo, self.GetDiskParams())
991

  
992
    for (idx, disk) in enumerate(result):
993
      self.assertTrue(isinstance(disk, objects.Disk))
994
      self.assertEqual(disk.dev_type, exp_dev_type)
995
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
996
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
997
      self.assertTrue(disk.children is None)
998

  
999
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1000
    instance._UpdateIvNames(base_index, result)
1001
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1002

  
1003
    return result
1004

  
1005
  def _CheckIvNames(self, disks, base_index, end_index):
1006
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1007
                     ["disk/%s" % i for i in range(base_index, end_index)])
1008

  
1009
  def testPlain(self):
1010
    disk_info = [{
1011
      constants.IDISK_SIZE: 1024,
1012
      constants.IDISK_MODE: constants.DISK_RDWR,
1013
      }, {
1014
      constants.IDISK_SIZE: 4096,
1015
      constants.IDISK_VG: "othervg",
1016
      constants.IDISK_MODE: constants.DISK_RDWR,
1017
      }]
1018

  
1019
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1020
                                   constants.LD_LV)
1021

  
1022
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1023
      ("testvg", "ec0-uq0.disk3"),
1024
      ("othervg", "ec0-uq1.disk4"),
1025
      ])
1026

  
1027
  def testFile(self):
1028
    # anything != DT_FILE would do here
1029
    self._SetUpLUWithTemplates([constants.DT_PLAIN])
1030
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1031
                      constants.DT_FILE, [], 0, NotImplemented)
1032
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1033
                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
1034

  
1035
    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1036
      disk_info = [{
1037
        constants.IDISK_SIZE: 80 * 1024,
1038
        constants.IDISK_MODE: constants.DISK_RDONLY,
1039
        }, {
1040
        constants.IDISK_SIZE: 4096,
1041
        constants.IDISK_MODE: constants.DISK_RDWR,
1042
        }, {
1043
        constants.IDISK_SIZE: 6 * 1024,
1044
        constants.IDISK_MODE: constants.DISK_RDWR,
1045
        }]
1046

  
1047
      self._SetUpLUWithTemplates([disk_template])
1048
      result = self._TestTrivialDisk(disk_template, disk_info, 2,
1049
        constants.LD_FILE, file_storage_dir="/tmp",
1050
        file_driver=constants.FD_BLKTAP)
1051

  
1052
      self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1053
        (constants.FD_BLKTAP, "/tmp/disk2"),
1054
        (constants.FD_BLKTAP, "/tmp/disk3"),
1055
        (constants.FD_BLKTAP, "/tmp/disk4"),
1056
        ])
1057

  
1058
  def testBlock(self):
1059
    disk_info = [{
1060
      constants.IDISK_SIZE: 8 * 1024,
1061
      constants.IDISK_MODE: constants.DISK_RDWR,
1062
      constants.IDISK_ADOPT: "/tmp/some/block/dev",
1063
      }]
1064

  
1065
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1066
                                   constants.LD_BLOCKDEV)
1067

  
1068
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1069
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1070
      ])
1071

  
1072
  def testRbd(self):
1073
    disk_info = [{
1074
      constants.IDISK_SIZE: 8 * 1024,
1075
      constants.IDISK_MODE: constants.DISK_RDONLY,
1076
      }, {
1077
      constants.IDISK_SIZE: 100 * 1024,
1078
      constants.IDISK_MODE: constants.DISK_RDWR,
1079
      }]
1080

  
1081
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1082
                                   constants.LD_RBD)
1083

  
1084
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1085
      ("rbd", "ec0-uq0.rbd.disk0"),
1086
      ("rbd", "ec0-uq1.rbd.disk1"),
1087
      ])
1088

  
1089
  def testDrbd8(self):
1090
    gdt = instance.GenerateDiskTemplate
1091
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1092
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1093

  
1094
    disk_info = [{
1095
      constants.IDISK_SIZE: 1024,
1096
      constants.IDISK_MODE: constants.DISK_RDWR,
1097
      }, {
1098
      constants.IDISK_SIZE: 100 * 1024,
1099
      constants.IDISK_MODE: constants.DISK_RDONLY,
1100
      constants.IDISK_METAVG: "metavg",
1101
      }, {
1102
      constants.IDISK_SIZE: 4096,
1103
      constants.IDISK_MODE: constants.DISK_RDWR,
1104
      constants.IDISK_VG: "vgxyz",
1105
      },
1106
      ]
1107

  
1108
    exp_logical_ids = [[
1109
      (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1110
      (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1111
      ], [
1112
      (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1113
      ("metavg", "ec0-uq1.disk1_meta"),
1114
      ], [
1115
      ("vgxyz", "ec0-uq2.disk2_data"),
1116
      (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1117
      ]]
1118

  
1119
    assert len(exp_logical_ids) == len(disk_info)
1120

  
1121
    map(lambda params: utils.ForceDictType(params,
1122
                                           constants.IDISK_PARAMS_TYPES),
1123
        disk_info)
1124

  
1125
    # Check if empty list of secondaries is rejected
1126
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1127
                      "inst827.example.com", "node1334.example.com", [],
1128
                      disk_info, NotImplemented, NotImplemented, 0,
1129
                      self.lu.LogInfo, self.GetDiskParams())
1130

  
1131
    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1132
                 "node1334.example.com", ["node12272.example.com"],
1133
                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1134
                 self.GetDiskParams())
1135

  
1136
    for (idx, disk) in enumerate(result):
1137
      self.assertTrue(isinstance(disk, objects.Disk))
1138
      self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1139
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1140
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1141

  
1142
      for child in disk.children:
1143
        self.assertTrue(isinstance(disk, objects.Disk))
1144
        self.assertEqual(child.dev_type, constants.LD_LV)
1145
        self.assertTrue(child.children is None)
1146

  
1147
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1148
                       exp_logical_ids[idx])
1149

  
1150
      self.assertEqual(len(disk.children), 2)
1151
      self.assertEqual(disk.children[0].size, disk.size)
1152
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1153

  
1154
    self._CheckIvNames(result, 0, len(disk_info))
1155
    instance._UpdateIvNames(0, result)
1156
    self._CheckIvNames(result, 0, len(disk_info))
1157

  
1158
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1159
      ("node1334.example.com", "node12272.example.com",
1160
       constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1161
      ("node1334.example.com", "node12272.example.com",
1162
       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1163
      ("node1334.example.com", "node12272.example.com",
1164
       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1165
      ])
1166

  
1167

  
1168
class _ConfigForDiskWipe:
1169
  def __init__(self, exp_node_uuid):
1170
    self._exp_node_uuid = exp_node_uuid
1171

  
1172
  def SetDiskID(self, device, node_uuid):
1173
    assert isinstance(device, objects.Disk)
1174
    assert node_uuid == self._exp_node_uuid
1175

  
1176
  def GetNodeName(self, node_uuid):
1177
    assert node_uuid == self._exp_node_uuid
1178
    return "name.of.expected.node"
1179

  
1180

  
1181
class _RpcForDiskWipe:
1182
  def __init__(self, exp_node, pause_cb, wipe_cb):
1183
    self._exp_node = exp_node
1184
    self._pause_cb = pause_cb
1185
    self._wipe_cb = wipe_cb
1186

  
1187
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1188
    assert node == self._exp_node
1189
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1190

  
1191
  def call_blockdev_wipe(self, node, bdev, offset, size):
1192
    assert node == self._exp_node
1193
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1194

  
1195

  
1196
class _DiskPauseTracker:
1197
  def __init__(self):
1198
    self.history = []
1199

  
1200
  def __call__(self, (disks, instance), pause):
1201
    assert not (set(disks) - set(instance.disks))
1202

  
1203
    self.history.extend((i.logical_id, i.size, pause)
1204
                        for i in disks)
1205

  
1206
    return (True, [True] * len(disks))
1207

  
1208

  
1209
class _DiskWipeProgressTracker:
1210
  def __init__(self, start_offset):
1211
    self._start_offset = start_offset
1212
    self.progress = {}
1213

  
1214
  def __call__(self, (disk, _), offset, size):
1215
    assert isinstance(offset, (long, int))
1216
    assert isinstance(size, (long, int))
1217

  
1218
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1219

  
1220
    assert offset >= self._start_offset
1221
    assert (offset + size) <= disk.size
1222

  
1223
    assert size > 0
1224
    assert size <= constants.MAX_WIPE_CHUNK
1225
    assert size <= max_chunk_size
1226

  
1227
    assert offset == self._start_offset or disk.logical_id in self.progress
1228

  
1229
    # Keep track of progress
1230
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1231

  
1232
    assert cur_progress == offset
1233

  
1234
    # Record progress
1235
    self.progress[disk.logical_id] += size
1236

  
1237
    return (True, None)
1238

  
1239

  
1240
class TestWipeDisks(unittest.TestCase):
1241
  def _FailingPauseCb(self, (disks, _), pause):
1242
    self.assertEqual(len(disks), 3)
1243
    self.assertTrue(pause)
1244
    # Simulate an RPC error
1245
    return (False, "error")
1246

  
1247
  def testPauseFailure(self):
1248
    node_name = "node1372.example.com"
1249

  
1250
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1251
                                     NotImplemented),
1252
                 cfg=_ConfigForDiskWipe(node_name))
1253

  
1254
    disks = [
1255
      objects.Disk(dev_type=constants.LD_LV),
1256
      objects.Disk(dev_type=constants.LD_LV),
1257
      objects.Disk(dev_type=constants.LD_LV),
1258
      ]
1259

  
1260
    inst = objects.Instance(name="inst21201",
1261
                            primary_node=node_name,
1262
                            disk_template=constants.DT_PLAIN,
1263
                            disks=disks)
1264

  
1265
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1266

  
1267
  def _FailingWipeCb(self, (disk, _), offset, size):
1268
    # This should only ever be called for the first disk
1269
    self.assertEqual(disk.logical_id, "disk0")
1270
    return (False, None)
1271

  
1272
  def testFailingWipe(self):
1273
    node_uuid = "node13445-uuid"
1274
    pt = _DiskPauseTracker()
1275

  
1276
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1277
                 cfg=_ConfigForDiskWipe(node_uuid))
1278

  
1279
    disks = [
1280
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1281
                   size=100 * 1024),
1282
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1283
                   size=500 * 1024),
1284
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
1285
      ]
1286

  
1287
    inst = objects.Instance(name="inst562",
1288
                            primary_node=node_uuid,
1289
                            disk_template=constants.DT_PLAIN,
1290
                            disks=disks)
1291

  
1292
    try:
1293
      instance.WipeDisks(lu, inst)
1294
    except errors.OpExecError, err:
1295
      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1296
    else:
1297
      self.fail("Did not raise exception")
1298

  
1299
    # Check if all disks were paused and resumed
1300
    self.assertEqual(pt.history, [
1301
      ("disk0", 100 * 1024, True),
1302
      ("disk1", 500 * 1024, True),
1303
      ("disk2", 256, True),
1304
      ("disk0", 100 * 1024, False),
1305
      ("disk1", 500 * 1024, False),
1306
      ("disk2", 256, False),
1307
      ])
1308

  
1309
  def _PrepareWipeTest(self, start_offset, disks):
1310
    node_name = "node-with-offset%s.example.com" % start_offset
1311
    pauset = _DiskPauseTracker()
1312
    progresst = _DiskWipeProgressTracker(start_offset)
1313

  
1314
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1315
                 cfg=_ConfigForDiskWipe(node_name))
1316

  
1317
    instance = objects.Instance(name="inst3560",
1318
                                primary_node=node_name,
1319
                                disk_template=constants.DT_PLAIN,
1320
                                disks=disks)
1321

  
1322
    return (lu, instance, pauset, progresst)
1323

  
1324
  def testNormalWipe(self):
1325
    disks = [
1326
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
1327
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1328
                   size=500 * 1024),
1329
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
1330
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
1331
                   size=constants.MAX_WIPE_CHUNK),
1332
      ]
1333

  
1334
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1335

  
1336
    instance.WipeDisks(lu, inst)
1337

  
1338
    self.assertEqual(pauset.history, [
1339
      ("disk0", 1024, True),
1340
      ("disk1", 500 * 1024, True),
1341
      ("disk2", 128, True),
1342
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1343
      ("disk0", 1024, False),
1344
      ("disk1", 500 * 1024, False),
1345
      ("disk2", 128, False),
1346
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1347
      ])
1348

  
1349
    # Ensure the complete disk has been wiped
1350
    self.assertEqual(progresst.progress,
1351
                     dict((i.logical_id, i.size) for i in disks))
1352

  
1353
  def testWipeWithStartOffset(self):
1354
    for start_offset in [0, 280, 8895, 1563204]:
1355
      disks = [
1356
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1357
                     size=128),
1358
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1359
                     size=start_offset + (100 * 1024)),
1360
        ]
1361

  
1362
      (lu, inst, pauset, progresst) = \
1363
        self._PrepareWipeTest(start_offset, disks)
1364

  
1365
      # Test start offset with only one disk
1366
      instance.WipeDisks(lu, inst,
1367
                         disks=[(1, disks[1], start_offset)])
1368

  
1369
      # Only the second disk may have been paused and wiped
1370
      self.assertEqual(pauset.history, [
1371
        ("disk1", start_offset + (100 * 1024), True),
1372
        ("disk1", start_offset + (100 * 1024), False),
1373
        ])
1374
      self.assertEqual(progresst.progress, {
1375
        "disk1": disks[1].size,
1376
        })
1377

  
1378

  
1379 662
class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
1380 663
  def testLessThanOneMebibyte(self):
1381 664
    for i in [1, 2, 7, 512, 1000, 1023]:
......
1408 691
        self.assertEqual(warnsize, (1024 * 1024) - j)
1409 692

  
1410 693

  
1411
class TestCopyLockList(unittest.TestCase):
1412
  def test(self):
1413
    self.assertEqual(instance.CopyLockList([]), [])
1414
    self.assertEqual(instance.CopyLockList(None), None)
1415
    self.assertEqual(instance.CopyLockList(locking.ALL_SET), locking.ALL_SET)
1416

  
1417
    names = ["foo", "bar"]
1418
    output = instance.CopyLockList(names)
1419
    self.assertEqual(names, output)
1420
    self.assertNotEqual(id(names), id(output), msg="List was not copied")
1421

  
1422

  
1423
class TestCheckOpportunisticLocking(unittest.TestCase):
1424
  class OpTest(opcodes.OpCode):
1425
    OP_PARAMS = [
1426
      ("opportunistic_locking", False, ht.TBool, None),
1427
      ("iallocator", None, ht.TMaybe(ht.TNonEmptyString), "")
1428
      ]
1429

  
1430
  @classmethod
1431
  def _MakeOp(cls, **kwargs):
1432
    op = cls.OpTest(**kwargs)
1433
    op.Validate(True)
1434
    return op
1435

  
1436
  def testMissingAttributes(self):
1437
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1438
                      object())
1439

  
1440
  def testDefaults(self):
1441
    op = self._MakeOp()
1442
    instance._CheckOpportunisticLocking(op)
1443

  
1444
  def test(self):
1445
    for iallocator in [None, "something", "other"]:
1446
      for opplock in [False, True]:
1447
        op = self._MakeOp(iallocator=iallocator,
1448
                          opportunistic_locking=opplock)
1449
        if opplock and not iallocator:
1450
          self.assertRaises(errors.OpPrereqError,
1451
                            instance._CheckOpportunisticLocking, op)
1452
        else:
1453
          instance._CheckOpportunisticLocking(op)
1454

  
1455

  
1456 694
class _OpTestVerifyErrors(opcodes.OpCode):
1457 695
  OP_PARAMS = [
1458 696
    ("debug_simulate_errors", False, ht.TBool, ""),
......
1798 1036
      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {},
1799 1037
                        bad_policy, group_policy=True)
1800 1038

  
1039

  
1040
class TestCopyLockList(unittest.TestCase):
1041
  def test(self):
1042
    self.assertEqual(instance_utils.CopyLockList([]), [])
1043
    self.assertEqual(instance_utils.CopyLockList(None), None)
1044
    self.assertEqual(instance_utils.CopyLockList(locking.ALL_SET),
1045
                     locking.ALL_SET)
1046

  
1047
    names = ["foo", "bar"]
1048
    output = instance_utils.CopyLockList(names)
1049
    self.assertEqual(names, output)
1050
    self.assertNotEqual(id(names), id(output), msg="List was not copied")
1051

  
1052

  
1801 1053
if __name__ == "__main__":
1802 1054
  testutils.GanetiTestProgram()
b/test/py/cmdlib/instance_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2008, 2011, 2012, 2013 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
"""Tests for LUInstance*
23

  
24
"""
25

  
26
import copy
27
import itertools
28
import unittest
29
import mock
30
import operator
31

  
32
from ganeti import compat
33
from ganeti import constants
34
from ganeti import errors
35
from ganeti import ht
36
from ganeti import opcodes
37
from ganeti import objects
38
from ganeti import rpc
39
from ganeti import utils
40
from ganeti.cmdlib import instance
41

  
42
from cmdlib.cmdlib_unittest import _StubComputeIPolicySpecViolation, _FakeLU
43

  
44
from testsupport import *
45

  
46
import testutils
47

  
48

  
49
class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
50
  def test(self):
51
    ispec = {
52
      constants.ISPEC_MEM_SIZE: 2048,
53
      constants.ISPEC_CPU_COUNT: 2,
54
      constants.ISPEC_DISK_COUNT: 1,
55
      constants.ISPEC_DISK_SIZE: [512],
56
      constants.ISPEC_NIC_COUNT: 0,
57
      constants.ISPEC_SPINDLE_USE: 1,
58
      }
59
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
60
                                            constants.DT_PLAIN)
61
    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
62
                                                        constants.DT_PLAIN,
63
                                                        _compute_fn=stub)
64
    self.assertEqual(ret, [])
65

  
66

  
67
class TestLUInstanceCreate(CmdlibTestCase):
68
  def setUp(self):
69
    super(TestLUInstanceCreate, self).setUp()
70

  
71
    self.net = self.cfg.AddNewNetwork()
72
    self.cfg.ConnectNetworkToGroup(self.net, self.group)
73

  
74
    self.node1 = self.cfg.AddNewNode()
75
    self.node2 = self.cfg.AddNewNode()
76

  
77
    self.rpc.call_os_get.side_effect = \
78
      lambda node, _: self.RpcResultsBuilder() \
79
                        .CreateSuccessfulNodeResult(node, self.os)
80

  
81
    hv_info = ("bootid",
82
               [{
83
                 "type": constants.ST_LVM_VG,
84
                 "storage_free": 10000
85
               }],
86
               ({"memory_free": 10000}, ))
87
    self.rpc.call_node_info.return_value = \
88
      self.RpcResultsBuilder() \
89
        .AddSuccessfulNode(self.master, hv_info) \
90
        .AddSuccessfulNode(self.node1, hv_info) \
91
        .AddSuccessfulNode(self.node2, hv_info) \
92
        .Build()
93

  
94
    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
95
      lambda node, _: self.RpcResultsBuilder() \
96
                        .CreateSuccessfulNodeResult(node, [])
97

  
98
    self.iallocator_cls.return_value.result = [self.node1.name, self.node2.name]
99

  
100
    self.diskless_op = opcodes.OpInstanceCreate(
101
      instance_name="diskless.test.com",
102
      pnode=self.master.name,
103
      disk_template=constants.DT_DISKLESS,
104
      mode=constants.INSTANCE_CREATE,
105
      nics=[{}],
106
      disks=[],
107
      os_type=self.os_name_variant)
108

  
109
    self.plain_op = opcodes.OpInstanceCreate(
110
      instance_name="plain.test.com",
111
      pnode=self.master.name,
112
      disk_template=constants.DT_PLAIN,
113
      mode=constants.INSTANCE_CREATE,
114
      nics=[{}],
115
      disks=[{
116
        constants.IDISK_SIZE: 1024
117
      }],
118
      os_type=self.os_name_variant)
119

  
120
    self.block_op = opcodes.OpInstanceCreate(
121
      instance_name="block.test.com",
122
      pnode=self.master.name,
123
      disk_template=constants.DT_BLOCK,
124
      mode=constants.INSTANCE_CREATE,
125
      nics=[{}],
126
      disks=[{
127
        constants.IDISK_SIZE: 1024,
128
        constants.IDISK_ADOPT: "/dev/disk/block0"
129
      }],
130
      os_type=self.os_name_variant)
131

  
132
    self.drbd_op = opcodes.OpInstanceCreate(
133
      instance_name="drbd.test.com",
134
      pnode=self.node1.name,
135
      snode=self.node2.name,
136
      disk_template=constants.DT_DRBD8,
137
      mode=constants.INSTANCE_CREATE,
138
      nics=[{}],
139
      disks=[{
140
        constants.IDISK_SIZE: 1024
141
      }],
142
      os_type=self.os_name_variant)
143

  
144
    self.file_op = opcodes.OpInstanceCreate(
145
      instance_name="file.test.com",
146
      pnode=self.node1.name,
147
      disk_template=constants.DT_FILE,
148
      mode=constants.INSTANCE_CREATE,
149
      nics=[{}],
150
      disks=[{
151
        constants.IDISK_SIZE: 1024
152
      }],
153
      os_type=self.os_name_variant)
154

  
155
  def testSimpleCreate(self):
156
    op = self.CopyOpCode(self.diskless_op)
157
    self.ExecOpCode(op)
158

  
159
  def testStrangeHostnameResolve(self):
160
    op = self.CopyOpCode(self.diskless_op)
161
    self.netutils_mod.GetHostname.return_value = \
162
      HostnameMock("random.host.com", "1.2.3.4")
163
    self.ExecOpCodeExpectOpPrereqError(
164
      op, "Resolved hostname .* does not look the same as given hostname")
165

  
166
  def testOpportunisticLockingNoIAllocator(self):
167
    op = self.CopyOpCode(self.diskless_op,
168
                         opportunistic_locking=True,
169
                         iallocator=None)
170
    self.ExecOpCodeExpectOpPrereqError(
171
      op, "Opportunistic locking is only available in combination with an"
172
          " instance allocator")
173

  
174
  def testNicWithNetAndMode(self):
175
    op = self.CopyOpCode(self.diskless_op,
176
                         nics=[{
177
                           constants.INIC_NETWORK: self.net.name,
178
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED
179
                         }])
180
    self.ExecOpCodeExpectOpPrereqError(
181
      op, "If network is given, no mode or link is allowed to be passed")
182

  
183
  def testVlanWithWrongMode(self):
184
    op = self.CopyOpCode(self.diskless_op,
185
                         nics=[{
186
                           constants.INIC_VLAN: ":1",
187
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED
188
                         }])
189
    self.ExecOpCodeExpectOpPrereqError(
190
      op, "VLAN is given, but network mode is not openvswitch")
191

  
192
  def testAutoIpNoNameCheck(self):
193
    op = self.CopyOpCode(self.diskless_op,
194
                         nics=[{
195
                           constants.INIC_IP: constants.VALUE_AUTO
196
                         }],
197
                         ip_check=False,
198
                         name_check=False)
199
    self.ExecOpCodeExpectOpPrereqError(
200
      op, "IP address set to auto but name checks have been skipped")
201

  
202
  def testAutoIp(self):
203
    op = self.CopyOpCode(self.diskless_op,
204
                         nics=[{
205
                           constants.INIC_IP: constants.VALUE_AUTO
206
                         }])
207
    self.ExecOpCode(op)
208

  
209
  def testPoolIpNoNetwork(self):
210
    op = self.CopyOpCode(self.diskless_op,
211
                         nics=[{
212
                           constants.INIC_IP: constants.NIC_IP_POOL
213
                         }])
214
    self.ExecOpCodeExpectOpPrereqError(
215
      op, "if ip=pool, parameter network must be passed too")
216

  
217
  def testValidIp(self):
218
    op = self.CopyOpCode(self.diskless_op,
219
                         nics=[{
220
                           constants.INIC_IP: "1.2.3.4"
221
                         }])
222
    self.ExecOpCode(op)
223

  
224
  def testRoutedNoIp(self):
225
    op = self.CopyOpCode(self.diskless_op,
226
                         nics=[{
227
                           constants.INIC_MODE: constants.NIC_MODE_ROUTED
228
                         }])
229
    self.ExecOpCodeExpectOpPrereqError(
230
      op, "Routed nic mode requires an ip address")
231

  
232
  def testValicMac(self):
233
    op = self.CopyOpCode(self.diskless_op,
234
                         nics=[{
235
                           constants.INIC_MAC: "f0:df:f4:a3:d1:cf"
236
                         }])
237
    self.ExecOpCode(op)
238

  
239
  def testValidNicParams(self):
240
    op = self.CopyOpCode(self.diskless_op,
241
                         nics=[{
242
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED,
243
                           constants.INIC_LINK: "br_mock"
244
                         }])
245
    self.ExecOpCode(op)
246

  
247
  def testValidNicParamsOpenVSwitch(self):
248
    op = self.CopyOpCode(self.diskless_op,
249
                         nics=[{
250
                           constants.INIC_MODE: constants.NIC_MODE_OVS,
251
                           constants.INIC_VLAN: "1"
252
                         }])
253
    self.ExecOpCode(op)
254

  
255
  def testNicNoneName(self):
256
    op = self.CopyOpCode(self.diskless_op,
257
                         nics=[{
258
                           constants.INIC_NAME: constants.VALUE_NONE
259
                         }])
260
    self.ExecOpCode(op)
261

  
262
  def testConflictingIP(self):
263
    op = self.CopyOpCode(self.diskless_op,
264
                         nics=[{
265
                           constants.INIC_IP: self.net.gateway[:-1] + "2"
266
                         }])
267
    self.ExecOpCodeExpectOpPrereqError(
268
      op, "The requested IP address .* belongs to network .*, but the target"
269
          " NIC does not.")
270

  
271
  def testVLanFormat(self):
272
    for vlan in [".pinky", ":bunny", ":1:pinky", "bunny"]:
273
      self.ResetMocks()
274
      op = self.CopyOpCode(self.diskless_op,
275
                           nics=[{
276
                             constants.INIC_VLAN: vlan
277
                           }])
278
      self.ExecOpCodeExpectOpPrereqError(
279
        op, "Specified VLAN parameter is invalid")
280

  
281
  def testPoolIp(self):
282
    op = self.CopyOpCode(self.diskless_op,
283
                         nics=[{
284
                           constants.INIC_IP: constants.NIC_IP_POOL,
285
                           constants.INIC_NETWORK: self.net.name
286
                         }])
287
    self.ExecOpCode(op)
288

  
289
  def testPoolIpUnconnectedNetwork(self):
290
    net = self.cfg.AddNewNetwork()
291
    op = self.CopyOpCode(self.diskless_op,
292
                         nics=[{
293
                           constants.INIC_IP: constants.NIC_IP_POOL,
294
                           constants.INIC_NETWORK: net.name
295
                         }])
296
    self.ExecOpCodeExpectOpPrereqError(
297
      op, "No netparams found for network .*.")
298

  
299
  def testIpNotInNetwork(self):
300
    op = self.CopyOpCode(self.diskless_op,
301
                         nics=[{
302
                           constants.INIC_IP: "1.2.3.4",
303
                           constants.INIC_NETWORK: self.net.name
304
                         }])
305
    self.ExecOpCodeExpectOpPrereqError(
306
      op, "IP address .* already in use or does not belong to network .*")
307

  
308
  def testMixAdoptAndNotAdopt(self):
309
    op = self.CopyOpCode(self.diskless_op,
310
                         disk_template=constants.DT_PLAIN,
311
                         disks=[{
312
                           constants.IDISK_ADOPT: "lv1"
313
                         }, {}])
314
    self.ExecOpCodeExpectOpPrereqError(
315
      op, "Either all disks are adopted or none is")
316

  
317
  def testMustAdoptWithoutAdopt(self):
318
    op = self.CopyOpCode(self.diskless_op,
319
                         disk_template=constants.DT_BLOCK,
320
                         disks=[{}])
321
    self.ExecOpCodeExpectOpPrereqError(
322
      op, "Disk template blockdev requires disk adoption, but no 'adopt'"
323
          " parameter given")
324

  
325
  def testDontAdoptWithAdopt(self):
326
    op = self.CopyOpCode(self.diskless_op,
327
                         disk_template=constants.DT_DRBD8,
328
                         disks=[{
329
                           constants.IDISK_ADOPT: "lv1"
330
                         }])
331
    self.ExecOpCodeExpectOpPrereqError(
332
      op, "Disk adoption is not supported for the 'drbd' disk template")
333

  
334
  def testAdoptWithIAllocator(self):
335
    op = self.CopyOpCode(self.diskless_op,
336
                         disk_template=constants.DT_PLAIN,
337
                         disks=[{
338
                           constants.IDISK_ADOPT: "lv1"
339
                         }],
340
                         iallocator="mock")
341
    self.ExecOpCodeExpectOpPrereqError(
342
      op, "Disk adoption not allowed with an iallocator script")
343

  
344
  def testAdoptWithImport(self):
345
    op = self.CopyOpCode(self.diskless_op,
346
                         disk_template=constants.DT_PLAIN,
347
                         disks=[{
348
                           constants.IDISK_ADOPT: "lv1"
349
                         }],
350
                         mode=constants.INSTANCE_IMPORT)
351
    self.ExecOpCodeExpectOpPrereqError(
352
      op, "Disk adoption not allowed for instance import")
353

  
354
  def testArgumentCombinations(self):
355
    op = self.CopyOpCode(self.diskless_op,
356
                         # start flag will be flipped
357
                         no_install=True,
358
                         start=True,
359
                         # no allowed combination
360
                         ip_check=True,
361
                         name_check=False)
362
    self.ExecOpCodeExpectOpPrereqError(
363
      op, "Cannot do IP address check without a name check")
364

  
365
  def testInvalidFileDriver(self):
366
    op = self.CopyOpCode(self.diskless_op,
367
                         file_driver="invalid_file_driver")
368
    self.ExecOpCodeExpectOpPrereqError(
369
      op, "Parameter 'OP_INSTANCE_CREATE.file_driver' fails validation")
370

  
371
  def testMissingSecondaryNode(self):
372
    op = self.CopyOpCode(self.diskless_op,
373
                         pnode=self.master.name,
374
                         disk_template=constants.DT_DRBD8)
375
    self.ExecOpCodeExpectOpPrereqError(
376
      op, "The networked disk templates need a mirror node")
377

  
378
  def testIgnoredSecondaryNode(self):
379
    op = self.CopyOpCode(self.diskless_op,
380
                         pnode=self.master.name,
381
                         snode=self.node1.name,
382
                         disk_template=constants.DT_PLAIN)
383
    try:
384
      self.ExecOpCode(op)
385
    except Exception:
386
      pass
387
    self.mcpu.assertLogContainsRegex(
388
      "Secondary node will be ignored on non-mirrored disk template")
389

  
390
  def testMissingOsType(self):
391
    op = self.CopyOpCode(self.diskless_op,
392
                         os_type=self.REMOVE)
393
    self.ExecOpCodeExpectOpPrereqError(op, "No guest OS specified")
394

  
395
  def testBlacklistedOs(self):
396
    self.cluster.blacklisted_os = [self.os_name_variant]
397
    op = self.CopyOpCode(self.diskless_op)
398
    self.ExecOpCodeExpectOpPrereqError(
399
      op, "Guest OS .* is not allowed for installation")
400

  
401
  def testMissingDiskTemplate(self):
402
    self.cluster.enabled_disk_templates = [constants.DT_DISKLESS]
403
    op = self.CopyOpCode(self.diskless_op,
404
                         disk_template=self.REMOVE)
405
    self.ExecOpCode(op)
406

  
407
  def testExistingInstance(self):
408
    inst = self.cfg.AddNewInstance()
409
    op = self.CopyOpCode(self.diskless_op,
410
                         instance_name=inst.name)
411
    self.ExecOpCodeExpectOpPrereqError(
412
      op, "Instance .* is already in the cluster")
413

  
414
  def testPlainInstance(self):
415
    op = self.CopyOpCode(self.plain_op)
416
    self.ExecOpCode(op)
417

  
418
  def testPlainIAllocator(self):
419
    op = self.CopyOpCode(self.plain_op,
420
                         pnode=self.REMOVE,
421
                         iallocator="mock")
422
    self.ExecOpCode(op)
423

  
424
  def testIAllocatorOpportunisticLocking(self):
425
    op = self.CopyOpCode(self.plain_op,
426
                         pnode=self.REMOVE,
427
                         iallocator="mock",
428
                         opportunistic_locking=True)
429
    self.ExecOpCode(op)
430

  
431
  def testFailingIAllocator(self):
432
    self.iallocator_cls.return_value.success = False
433
    op = self.CopyOpCode(self.plain_op,
434
                         pnode=self.REMOVE,
435
                         iallocator="mock")
436
    self.ExecOpCodeExpectOpPrereqError(
437
      op, "Can't compute nodes using iallocator")
438

  
439
  def testDrbdInstance(self):
440
    op = self.CopyOpCode(self.drbd_op)
441
    self.ExecOpCode(op)
442

  
443
  def testDrbdIAllocator(self):
444
    op = self.CopyOpCode(self.drbd_op,
445
                         pnode=self.REMOVE,
446
                         snode=self.REMOVE,
447
                         iallocator="mock")
448
    self.ExecOpCode(op)
449

  
450
  def testFileInstance(self):
451
    op = self.CopyOpCode(self.file_op)
452
    self.ExecOpCode(op)
453

  
454
  def testFileInstanceNoClusterStorage(self):
455
    self.cluster.file_storage_dir = None
456
    op = self.CopyOpCode(self.file_op)
457
    self.ExecOpCodeExpectOpPrereqError(
458
      op, "Cluster file storage dir not defined")
459

  
460
  def testFileInstanceAdditionalPath(self):
461
    op = self.CopyOpCode(self.file_op,
462
                         file_storage_dir="mock_dir")
463
    self.ExecOpCode(op)
464

  
465
  def testIdentifyDefaults(self):
466
    op = self.CopyOpCode(self.plain_op,
467
                         hvparams={
468
                           constants.HV_BOOT_ORDER: "cd"
469
                         },
470
                         beparams=constants.BEC_DEFAULTS.copy(),
471
                         nics=[{
472
                           constants.NIC_MODE: constants.NIC_MODE_BRIDGED
473
                         }],
474
                         osparams={
475
                           self.os_name_variant: {}
476
                         },
477
                         identify_defaults=True)
478
    self.ExecOpCode(op)
479

  
480
    inst = self.cfg.GetAllInstancesInfo().values()[0]
481
    self.assertEqual(0, len(inst.hvparams))
482
    self.assertEqual(0, len(inst.beparams))
483
    assert self.os_name_variant not in inst.osparams or \
484
            len(inst.osparams[self.os_name_variant]) == 0
485

  
486
  def testOfflineNode(self):
487
    self.node1.offline = True
488
    op = self.CopyOpCode(self.diskless_op,
489
                         pnode=self.node1.name)
490
    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use offline primary node")
491

  
492
  def testDrainedNode(self):
493
    self.node1.drained = True
494
    op = self.CopyOpCode(self.diskless_op,
495
                         pnode=self.node1.name)
496
    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use drained primary node")
497

  
498
  def testNonVmCapableNode(self):
499
    self.node1.vm_capable = False
500
    op = self.CopyOpCode(self.diskless_op,
501
                         pnode=self.node1.name)
502
    self.ExecOpCodeExpectOpPrereqError(
503
      op, "Cannot use non-vm_capable primary node")
504

  
505
  def testNonEnabledHypervisor(self):
506
    self.cluster.enabled_hypervisors = [constants.HT_XEN_HVM]
507
    op = self.CopyOpCode(self.diskless_op,
508
                         hypervisor=constants.HT_FAKE)
509
    self.ExecOpCodeExpectOpPrereqError(
510
      op, "Selected hypervisor .* not enabled in the cluster")
511

  
512
  def testAddTag(self):
513
    op = self.CopyOpCode(self.diskless_op,
514
                         tags=["tag"])
515
    self.ExecOpCode(op)
516

  
517
  def testInvalidTag(self):
518
    op = self.CopyOpCode(self.diskless_op,
519
                         tags=["too_long" * 20])
520
    self.ExecOpCodeExpectException(op, errors.TagError, "Tag too long")
521

  
522
  def testPingableInstanceName(self):
523
    self.netutils_mod.TcpPing.return_value = True
524
    op = self.CopyOpCode(self.diskless_op)
525
    self.ExecOpCodeExpectOpPrereqError(
526
      op, "IP .* of instance diskless.test.com already in use")
527

  
528
  def testPrimaryIsSecondaryNode(self):
529
    op = self.CopyOpCode(self.drbd_op,
530
                         snode=self.drbd_op.pnode)
531
    self.ExecOpCodeExpectOpPrereqError(
532
      op, "The secondary node cannot be the primary node")
533

  
534
  def testPrimarySecondaryDifferentNodeGroups(self):
535
    group = self.cfg.AddNewNodeGroup()
536
    self.node2.group = group.uuid
537
    op = self.CopyOpCode(self.drbd_op)
538
    self.ExecOpCode(op)
539
    self.mcpu.assertLogContainsRegex(
540
      "The primary and secondary nodes are in two different node groups")
541

  
542
  def testExclusiveStorageUnsupportedDiskTemplate(self):
543
    self.node1.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
544
    op = self.CopyOpCode(self.drbd_op)
545
    self.ExecOpCodeExpectOpPrereqError(
546
      op, "Disk template drbd not supported with exclusive storage")
547

  
548
  def testAdoptPlain(self):
549
    self.rpc.call_lv_list.return_value = \
550
      self.RpcResultsBuilder() \
551
        .AddSuccessfulNode(self.master, {
552
          "xenvg/mock_disk_1": (10000, None, False)
553
        }) \
554
        .Build()
555
    op = self.CopyOpCode(self.plain_op)
556
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
557
    self.ExecOpCode(op)
558

  
559
  def testAdoptPlainMissingLv(self):
560
    self.rpc.call_lv_list.return_value = \
561
      self.RpcResultsBuilder() \
562
        .AddSuccessfulNode(self.master, {}) \
563
        .Build()
564
    op = self.CopyOpCode(self.plain_op)
565
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
566
    self.ExecOpCodeExpectOpPrereqError(op, "Missing logical volume")
567

  
568
  def testAdoptPlainOnlineLv(self):
569
    self.rpc.call_lv_list.return_value = \
570
      self.RpcResultsBuilder() \
571
        .AddSuccessfulNode(self.master, {
572
          "xenvg/mock_disk_1": (10000, None, True)
573
        }) \
574
        .Build()
575
    op = self.CopyOpCode(self.plain_op)
576
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
577
    self.ExecOpCodeExpectOpPrereqError(
578
      op, "Online logical volumes found, cannot adopt")
579

  
580
  def testAdoptBlock(self):
581
    self.rpc.call_bdev_sizes.return_value = \
582
      self.RpcResultsBuilder() \
583
        .AddSuccessfulNode(self.master, {
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff