Honor disks_active of instance when adding disks
[ganeti-local] / test / py / cmdlib / instance_unittest.py
index baeda1c..0ea7191 100644 (file)
@@ -159,7 +159,7 @@ class TestLUInstanceCreate(CmdlibTestCase):
   def testStrangeHostnameResolve(self):
     op = self.CopyOpCode(self.diskless_op)
     self.netutils_mod.GetHostname.return_value = \
-      HostnameMock("random.host.com", "1.2.3.4")
+      HostnameMock("random.host.example.com", "203.0.113.1")
     self.ExecOpCodeExpectOpPrereqError(
       op, "Resolved hostname .* does not look the same as given hostname")
 
@@ -217,7 +217,7 @@ class TestLUInstanceCreate(CmdlibTestCase):
   def testValidIp(self):
     op = self.CopyOpCode(self.diskless_op,
                          nics=[{
-                           constants.INIC_IP: "1.2.3.4"
+                           constants.INIC_IP: "203.0.113.1"
                          }])
     self.ExecOpCode(op)
 
@@ -299,7 +299,7 @@ class TestLUInstanceCreate(CmdlibTestCase):
   def testIpNotInNetwork(self):
     op = self.CopyOpCode(self.diskless_op,
                          nics=[{
-                           constants.INIC_IP: "1.2.3.4",
+                           constants.INIC_IP: "203.0.113.1",
                            constants.INIC_NETWORK: self.net.name
                          }])
     self.ExecOpCodeExpectOpPrereqError(
@@ -708,7 +708,7 @@ disk1_dump=mock_path
 nic0_mode=bridged
 nic0_link=br_mock
 nic0_mac=f6:ab:f4:45:d1:af
-nic0_ip=123.123.123.1
+nic0_ip=192.0.2.1
 tags=tag1 tag2
 hypervisor=xen-hvm
 [hypervisor]
@@ -1083,7 +1083,7 @@ class TestGenerateDiskTemplate(CmdlibTestCase):
       }]
 
     result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
-                                   constants.LD_LV)
+                                   constants.DT_PLAIN)
 
     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
       ("xenvg", "ec1-uq0.disk3"),
@@ -1112,7 +1112,7 @@ class TestGenerateDiskTemplate(CmdlibTestCase):
 
       self.cluster.enabled_disk_templates = [disk_template]
       result = self._TestTrivialDisk(
-        disk_template, disk_info, 2, constants.LD_FILE,
+        disk_template, disk_info, 2, disk_template,
         file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
 
       self.assertEqual(map(operator.attrgetter("logical_id"), result), [
@@ -1129,7 +1129,7 @@ class TestGenerateDiskTemplate(CmdlibTestCase):
       }]
 
     result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
-                                   constants.LD_BLOCKDEV)
+                                   constants.DT_BLOCK)
 
     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
       (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
@@ -1145,7 +1145,7 @@ class TestGenerateDiskTemplate(CmdlibTestCase):
       }]
 
     result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
-                                   constants.LD_RBD)
+                                   constants.DT_RBD)
 
     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
       ("rbd", "ec1-uq0.rbd.disk0"),
@@ -1154,7 +1154,7 @@ class TestGenerateDiskTemplate(CmdlibTestCase):
 
   def testDrbd8(self):
     gdt = instance.GenerateDiskTemplate
-    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
+    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
     drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
 
     disk_info = [{
@@ -1202,13 +1202,13 @@ class TestGenerateDiskTemplate(CmdlibTestCase):
 
     for (idx, disk) in enumerate(result):
       self.assertTrue(isinstance(disk, objects.Disk))
-      self.assertEqual(disk.dev_type, constants.LD_DRBD8)
+      self.assertEqual(disk.dev_type, constants.DT_DRBD8)
       self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
       self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
 
       for child in disk.children:
         self.assertTrue(isinstance(disk, objects.Disk))
-        self.assertEqual(child.dev_type, constants.LD_LV)
+        self.assertEqual(child.dev_type, constants.DT_PLAIN)
         self.assertTrue(child.children is None)
 
       self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
@@ -1249,10 +1249,6 @@ class _ConfigForDiskWipe:
   def __init__(self, exp_node_uuid):
     self._exp_node_uuid = exp_node_uuid
 
-  def SetDiskID(self, device, node_uuid):
-    assert isinstance(device, objects.Disk)
-    assert node_uuid == self._exp_node_uuid
-
   def GetNodeName(self, node_uuid):
     assert node_uuid == self._exp_node_uuid
     return "name.of.expected.node"
@@ -1319,9 +1315,9 @@ class TestWipeDisks(unittest.TestCase):
                  cfg=_ConfigForDiskWipe(node_name))
 
     disks = [
-      objects.Disk(dev_type=constants.LD_LV),
-      objects.Disk(dev_type=constants.LD_LV),
-      objects.Disk(dev_type=constants.LD_LV),
+      objects.Disk(dev_type=constants.DT_PLAIN),
+      objects.Disk(dev_type=constants.DT_PLAIN),
+      objects.Disk(dev_type=constants.DT_PLAIN),
       ]
 
     inst = objects.Instance(name="inst21201",
@@ -1344,11 +1340,11 @@ class TestWipeDisks(unittest.TestCase):
                  cfg=_ConfigForDiskWipe(node_uuid))
 
     disks = [
-      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
                    size=100 * 1024),
-      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
                    size=500 * 1024),
-      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
       ]
 
     inst = objects.Instance(name="inst562",
@@ -1390,11 +1386,11 @@ class TestWipeDisks(unittest.TestCase):
 
   def testNormalWipe(self):
     disks = [
-      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
-      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0", size=1024),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
                    size=500 * 1024),
-      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
-      objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=128),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk3",
                    size=constants.MAX_WIPE_CHUNK),
       ]
 
@@ -1420,9 +1416,9 @@ class TestWipeDisks(unittest.TestCase):
   def testWipeWithStartOffset(self):
     for start_offset in [0, 280, 8895, 1563204]:
       disks = [
-        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
+        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
                      size=128),
-        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
+        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
                      size=start_offset + (100 * 1024)),
         ]
 
@@ -1713,5 +1709,565 @@ class TestLUInstanceMultiAlloc(CmdlibTestCase):
       op, "Can't compute nodes using iallocator")
 
 
+class TestLUInstanceSetParams(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceSetParams, self).setUp()
+
+    self.inst = self.cfg.AddNewInstance()
+    self.op = opcodes.OpInstanceSetParams(instance_name=self.inst.name)
+
+    self.running_inst = \
+      self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
+    self.running_op = \
+      opcodes.OpInstanceSetParams(instance_name=self.running_inst.name)
+
+    self.snode = self.cfg.AddNewNode()
+
+    self.mocked_storage_type = constants.ST_LVM_VG
+    self.mocked_storage_free = 10000
+    self.mocked_master_cpu_total = 16
+    self.mocked_master_memory_free = 2048
+    self.mocked_snode_cpu_total = 16
+    self.mocked_snode_memory_free = 512
+
+    self.mocked_running_inst_memory = 1024
+    self.mocked_running_inst_vcpus = 8
+    self.mocked_running_inst_state = "running"
+    self.mocked_running_inst_time = 10938474
+
+    bootid = "mock_bootid"
+    storage_info = [
+      {
+        "type": self.mocked_storage_type,
+        "storage_free": self.mocked_storage_free
+      }
+    ]
+    hv_info_master = {
+      "cpu_total": self.mocked_master_cpu_total,
+      "memory_free": self.mocked_master_memory_free
+    }
+    hv_info_snode = {
+      "cpu_total": self.mocked_snode_cpu_total,
+      "memory_free": self.mocked_snode_memory_free
+    }
+
+    self.rpc.call_node_info.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master,
+                           (bootid, storage_info, (hv_info_master, ))) \
+        .AddSuccessfulNode(self.snode,
+                           (bootid, storage_info, (hv_info_snode, ))) \
+        .Build()
+
+    def _InstanceInfo(_, instance, __, ___):
+      if instance == self.inst.name:
+        return self.RpcResultsBuilder() \
+          .CreateSuccessfulNodeResult(self.master, None)
+      elif instance == self.running_inst.name:
+        return self.RpcResultsBuilder() \
+          .CreateSuccessfulNodeResult(
+            self.master, {
+              "memory": self.mocked_running_inst_memory,
+              "vcpus": self.mocked_running_inst_vcpus,
+              "state": self.mocked_running_inst_state,
+              "time": self.mocked_running_inst_time
+            })
+      else:
+        raise AssertionError()
+    self.rpc.call_instance_info.side_effect = _InstanceInfo
+
+    self.rpc.call_bridges_exist.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+
+    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
+      lambda node, _: self.RpcResultsBuilder() \
+                        .CreateSuccessfulNodeResult(node, [])
+
+    self.rpc.call_blockdev_shutdown.side_effect = \
+      lambda node, _: self.RpcResultsBuilder() \
+                        .CreateSuccessfulNodeResult(node, [])
+
+  def testNoChanges(self):
+    op = self.CopyOpCode(self.op)
+    self.ExecOpCodeExpectOpPrereqError(op, "No changes submitted")
+
+  def testGlobalHvparams(self):
+    op = self.CopyOpCode(self.op,
+                         hvparams={constants.HV_MIGRATION_PORT: 1234})
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "hypervisor parameters are global and cannot be customized")
+
+  def testHvparams(self):
+    op = self.CopyOpCode(self.op,
+                         hvparams={constants.HV_BOOT_ORDER: "cd"})
+    self.ExecOpCode(op)
+
+  def testDisksAndDiskTemplate(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_PLAIN,
+                         disks=[[constants.DDM_ADD, -1, {}]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk template conversion and other disk changes not supported at"
+          " the same time")
+
+  def testDiskTemplateToMirroredNoRemoteNode(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_DRBD8)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Changing the disk template to a mirrored one requires specifying"
+          " a secondary node")
+
+  def testPrimaryNodeToOldPrimaryNode(self):
+    op = self.CopyOpCode(self.op,
+                         pnode=self.master.name)
+    self.ExecOpCode(op)
+
+  def testPrimaryNodeChange(self):
+    node = self.cfg.AddNewNode()
+    op = self.CopyOpCode(self.op,
+                         pnode=node.name)
+    self.ExecOpCode(op)
+
+  def testPrimaryNodeChangeRunningInstance(self):
+    node = self.cfg.AddNewNode()
+    op = self.CopyOpCode(self.running_op,
+                         pnode=node.name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Instance is still running")
+
+  def testOsChange(self):
+    os = self.cfg.CreateOs(supported_variants=[])
+    self.rpc.call_os_get.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, os)
+    op = self.CopyOpCode(self.op,
+                         os_name=os.name)
+    self.ExecOpCode(op)
+
+  def testVCpuChange(self):
+    op = self.CopyOpCode(self.op,
+                         beparams={
+                           constants.BE_VCPUS: 4
+                         })
+    self.ExecOpCode(op)
+
+  def testWrongCpuMask(self):
+    op = self.CopyOpCode(self.op,
+                         beparams={
+                           constants.BE_VCPUS: 4
+                         },
+                         hvparams={
+                           constants.HV_CPU_MASK: "1,2:3,4"
+                         })
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Number of vCPUs .* does not match the CPU mask .*")
+
+  def testCorrectCpuMask(self):
+    op = self.CopyOpCode(self.op,
+                         beparams={
+                           constants.BE_VCPUS: 4
+                         },
+                         hvparams={
+                           constants.HV_CPU_MASK: "1,2:3,4:all:1,4"
+                         })
+    self.ExecOpCode(op)
+
+  def testOsParams(self):
+    op = self.CopyOpCode(self.op,
+                         osparams={
+                           self.os.supported_parameters[0]: "test_param_val"
+                         })
+    self.ExecOpCode(op)
+
+  def testIncreaseMemoryTooMuch(self):
+    op = self.CopyOpCode(self.running_op,
+                         beparams={
+                           constants.BE_MAXMEM:
+                             self.mocked_master_memory_free * 2
+                         })
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "This change will prevent the instance from starting")
+
+  def testIncreaseMemory(self):
+    op = self.CopyOpCode(self.running_op,
+                         beparams={
+                           constants.BE_MAXMEM: self.mocked_master_memory_free
+                         })
+    self.ExecOpCode(op)
+
+  def testIncreaseMemoryTooMuchForSecondary(self):
+    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP,
+                                   disk_template=constants.DT_DRBD8,
+                                   secondary_node=self.snode)
+    self.rpc.call_instance_info.side_effect = [
+      self.RpcResultsBuilder()
+        .CreateSuccessfulNodeResult(self.master,
+                                    {
+                                      "memory":
+                                        self.mocked_snode_memory_free * 2,
+                                      "vcpus": self.mocked_running_inst_vcpus,
+                                      "state": self.mocked_running_inst_state,
+                                      "time": self.mocked_running_inst_time
+                                    })]
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         beparams={
+                           constants.BE_MAXMEM:
+                             self.mocked_snode_memory_free * 2,
+                           constants.BE_AUTO_BALANCE: True
+                         })
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "This change will prevent the instance from failover to its"
+          " secondary node")
+
+  def testInvalidRuntimeMemory(self):
+    op = self.CopyOpCode(self.running_op,
+                         runtime_mem=self.mocked_master_memory_free * 2)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance .* must have memory between .* and .* of memory")
+
+  def testIncreaseRuntimeMemory(self):
+    op = self.CopyOpCode(self.running_op,
+                         runtime_mem=self.mocked_master_memory_free,
+                         beparams={
+                           constants.BE_MAXMEM: self.mocked_master_memory_free
+                         })
+    self.ExecOpCode(op)
+
+  def testAddNicWithPoolIpNoNetwork(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: constants.NIC_IP_POOL
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "If ip=pool, parameter network cannot be none")
+
+  def testAddNicWithPoolIp(self):
+    net = self.cfg.AddNewNetwork()
+    self.cfg.ConnectNetworkToGroup(net, self.group)
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: constants.NIC_IP_POOL,
+                                  constants.INIC_NETWORK: net.name
+                                })])
+    self.ExecOpCode(op)
+
+  def testAddNicWithInvalidIp(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: "invalid"
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Invalid IP address")
+
+  def testAddNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1, {})])
+    self.ExecOpCode(op)
+
+  def testAddNicWithIp(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: "2.3.1.4"
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNicRoutedWithoutIp(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_MODE: constants.NIC_MODE_ROUTED
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Cannot set the NIC IP address to None on a routed NIC")
+
+  def testModifyNicSetMac(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_MAC: "0a:12:95:15:bf:75"
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNicWithPoolIpNoNetwork(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, -1,
+                                {
+                                  constants.INIC_IP: constants.NIC_IP_POOL
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "ip=pool, but no network found")
+
+  def testModifyNicSetNet(self):
+    old_net = self.cfg.AddNewNetwork()
+    self.cfg.ConnectNetworkToGroup(old_net, self.group)
+    inst = self.cfg.AddNewInstance(nics=[
+      self.cfg.CreateNic(network=old_net,
+                         ip="198.51.100.2")])
+
+    new_net = self.cfg.AddNewNetwork(mac_prefix="be")
+    self.cfg.ConnectNetworkToGroup(new_net, self.group)
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_NETWORK: new_net.name
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNicSetLinkWhileConnected(self):
+    old_net = self.cfg.AddNewNetwork()
+    self.cfg.ConnectNetworkToGroup(old_net, self.group)
+    inst = self.cfg.AddNewInstance(nics=[
+      self.cfg.CreateNic(network=old_net)])
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_LINK: "mock_link"
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Not allowed to change link or mode of a NIC that is connected"
+          " to a network")
+
+  def testModifyNicSetNetAndIp(self):
+    net = self.cfg.AddNewNetwork(mac_prefix="be", network="123.123.123.0/24")
+    self.cfg.ConnectNetworkToGroup(net, self.group)
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_NETWORK: net.name,
+                                  constants.INIC_IP: "123.123.123.1"
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0, {})])
+    self.ExecOpCode(op)
+
+  def testRemoveLastNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_REMOVE, 0, {})])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "violates policy")
+
+  def testRemoveNic(self):
+    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
+                                         self.cfg.CreateNic()])
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         nics=[(constants.DDM_REMOVE, 0, {})])
+    self.ExecOpCode(op)
+
+  def testSetOffline(self):
+    op = self.CopyOpCode(self.op,
+                         offline=True)
+    self.ExecOpCode(op)
+
+  def testUnsetOffline(self):
+    op = self.CopyOpCode(self.op,
+                         offline=False)
+    self.ExecOpCode(op)
+
+  def testAddDiskInvalidMode(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_MODE: "invalid"
+                                 }]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Invalid disk access mode 'invalid'")
+
+  def testAddDiskMissingSize(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1, {}]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Required disk parameter 'size' missing")
+
+  def testAddDiskInvalidSize(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: "invalid"
+                                 }]])
+    self.ExecOpCodeExpectException(
+      op, errors.TypeEnforcementError, "is not a valid size")
+
+  def testAddDiskRunningInstanceNoWaitForSync(self):
+    op = self.CopyOpCode(self.running_op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]],
+                         wait_for_sync=False)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't add a disk to an instance with activated disks"
+          " and --no-wait-for-sync given.")
+
+  def testAddDiskDownInstance(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]])
+    self.ExecOpCode(op)
+
+    self.assertTrue(self.rpc.call_blockdev_shutdown.called)
+
+  def testAddDiskRunningInstance(self):
+    op = self.CopyOpCode(self.running_op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]])
+    self.ExecOpCode(op)
+
+    self.assertFalse(self.rpc.call_blockdev_shutdown.called)
+
+  def testAddDiskNoneName(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024,
+                                   constants.IDISK_NAME: constants.VALUE_NONE
+                                 }]])
+    self.ExecOpCode(op)
+
+  def testModifyDiskWithSize(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_MODIFY, 0,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk size change not possible, use grow-disk")
+
+  def testModifyDiskWithRandomParams(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_MODIFY, 0,
+                                 {
+                                   constants.IDISK_METAVG: "new_meta_vg",
+                                   constants.IDISK_MODE: "invalid",
+                                   constants.IDISK_NAME: "new_name"
+                                 }]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk modification doesn't support additional arbitrary parameters")
+
+  def testModifyDiskUnsetName(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_MODIFY, 0,
+                                  {
+                                    constants.IDISK_NAME: constants.VALUE_NONE
+                                  }]])
+    self.ExecOpCode(op)
+
+  def testSetOldDiskTemplate(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=self.inst.disk_template)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance already has disk template")
+
+  def testSetDisabledDiskTemplate(self):
+    self.cfg.SetEnabledDiskTemplates([self.inst.disk_template])
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_EXT)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk template .* is not enabled for this cluster")
+
+  def testInvalidDiskTemplateConversion(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_EXT)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Unsupported disk template conversion from .* to .*")
+
+  def testConvertToDRBDWithSecondarySameAsPrimary(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_DRBD8,
+                         remote_node=self.master.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Given new secondary node .* is the same as the primary node"
+          " of the instance")
+
+  def testConvertPlainToDRBD(self):
+    self.rpc.call_blockdev_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_blockdev_getmirrorstatus.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
+
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_DRBD8,
+                         remote_node=self.snode.name)
+    self.ExecOpCode(op)
+
+  def testConvertDRBDToPlain(self):
+    self.inst.disks = [self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
+                                           primary_node=self.master,
+                                           secondary_node=self.snode)]
+    self.inst.disk_template = constants.DT_DRBD8
+    self.rpc.call_blockdev_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_blockdev_remove.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+    self.rpc.call_blockdev_getmirrorstatus.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
+
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_PLAIN)
+    self.ExecOpCode(op)
+
+
+class TestLUInstanceChangeGroup(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceChangeGroup, self).setUp()
+
+    self.group2 = self.cfg.AddNewNodeGroup()
+    self.node2 = self.cfg.AddNewNode(group=self.group2)
+    self.inst = self.cfg.AddNewInstance()
+    self.op = opcodes.OpInstanceChangeGroup(instance_name=self.inst.name)
+
+  def testTargetGroupIsInstanceGroup(self):
+    op = self.CopyOpCode(self.op,
+                         target_groups=[self.group.name])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't use group\(s\) .* as targets, they are used by the"
+          " instance .*")
+
+  def testNoTargetGroups(self):
+    inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                                   primary_node=self.master,
+                                   secondary_node=self.node2)
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "There are no possible target groups")
+
+  def testFailingIAllocator(self):
+    self.iallocator_cls.return_value.success = False
+    op = self.CopyOpCode(self.op)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't compute solution for changing group of instance .*"
+          " using iallocator .*")
+
+  def testChangeGroup(self):
+    self.iallocator_cls.return_value.success = True
+    self.iallocator_cls.return_value.result = ([], [], [])
+    op = self.CopyOpCode(self.op)
+
+    self.ExecOpCode(op)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()