Merge branch 'devel-2.6'
[ganeti-local] / test / ganeti.cmdlib_unittest.py
index 1f3eb5c..3cb0c40 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2008, 2011 Google Inc.
+# Copyright (C) 2008, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -29,6 +29,7 @@ import tempfile
 import shutil
 import operator
 import itertools
+import copy
 
 from ganeti import constants
 from ganeti import mcpu
@@ -41,6 +42,9 @@ from ganeti import ht
 from ganeti import objects
 from ganeti import compat
 from ganeti import rpc
+from ganeti import locking
+from ganeti import pathutils
+from ganeti.masterd import iallocator
 from ganeti.hypervisor import hv_xen
 
 import testutils
@@ -105,23 +109,26 @@ class TestIAllocatorChecks(testutils.GanetiTestCase):
     c_i = lambda: cmdlib._CheckIAllocatorOrNode(lu, "iallocator", "node")
 
     # Neither node nor iallocator given
-    op.iallocator = None
-    op.node = None
-    c_i()
-    self.assertEqual(lu.op.iallocator, default_iallocator)
-    self.assertEqual(lu.op.node, None)
+    for n in (None, []):
+      op.iallocator = None
+      op.node = n
+      c_i()
+      self.assertEqual(lu.op.iallocator, default_iallocator)
+      self.assertEqual(lu.op.node, n)
 
     # Both, iallocator and node given
-    op.iallocator = "test"
-    op.node = "test"
-    self.assertRaises(errors.OpPrereqError, c_i)
+    for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT):
+      op.iallocator = a
+      op.node = "test"
+      self.assertRaises(errors.OpPrereqError, c_i)
 
     # Only iallocator given
-    op.iallocator = other_iallocator
-    op.node = None
-    c_i()
-    self.assertEqual(lu.op.iallocator, other_iallocator)
-    self.assertEqual(lu.op.node, None)
+    for n in (None, []):
+      op.iallocator = other_iallocator
+      op.node = n
+      c_i()
+      self.assertEqual(lu.op.iallocator, other_iallocator)
+      self.assertEqual(lu.op.node, n)
 
     # Only node given
     op.iallocator = None
@@ -130,6 +137,13 @@ class TestIAllocatorChecks(testutils.GanetiTestCase):
     self.assertEqual(lu.op.iallocator, None)
     self.assertEqual(lu.op.node, "node")
 
+    # Asked for default iallocator, no node given
+    op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT
+    op.node = None
+    c_i()
+    self.assertEqual(lu.op.iallocator, default_iallocator)
+    self.assertEqual(lu.op.node, None)
+
     # No node, iallocator or default iallocator
     op.iallocator = None
     op.node = None
@@ -302,50 +316,50 @@ class TestClusterVerifyFiles(unittest.TestCase):
     cluster = objects.Cluster(modify_etc_hosts=True,
                               enabled_hypervisors=[constants.HT_XEN_HVM])
     files_all = set([
-      constants.CLUSTER_DOMAIN_SECRET_FILE,
-      constants.RAPI_CERT_FILE,
-      constants.RAPI_USERS_FILE,
+      pathutils.CLUSTER_DOMAIN_SECRET_FILE,
+      pathutils.RAPI_CERT_FILE,
+      pathutils.RAPI_USERS_FILE,
       ])
     files_opt = set([
-      constants.RAPI_USERS_FILE,
+      pathutils.RAPI_USERS_FILE,
       hv_xen.XL_CONFIG_FILE,
-      constants.VNC_PASSWORD_FILE,
+      pathutils.VNC_PASSWORD_FILE,
       ])
     files_mc = set([
-      constants.CLUSTER_CONF_FILE,
+      pathutils.CLUSTER_CONF_FILE,
       ])
     files_vm = set([
       hv_xen.XEND_CONFIG_FILE,
       hv_xen.XL_CONFIG_FILE,
-      constants.VNC_PASSWORD_FILE,
+      pathutils.VNC_PASSWORD_FILE,
       ])
     nvinfo = {
       master_name: rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
-          constants.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
-          constants.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
-          constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
+          pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
+          pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
+          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
         }})),
       "node2.example.com": rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
-          constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
+          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
           }
         })),
       "node3.example.com": rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
-          constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
-          constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
+          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
+          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
           }
         })),
       "node4.example.com": rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
-          constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
-          constants.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
-          constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
-          constants.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
+          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
+          pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
+          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
+          pathutils.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
           }
         })),
@@ -360,21 +374,21 @@ class TestClusterVerifyFiles(unittest.TestCase):
     self.assertEqual(sorted(errors), sorted([
       (None, ("File %s found with 2 different checksums (variant 1 on"
               " node2.example.com, node3.example.com, node4.example.com;"
-              " variant 2 on master.example.com)" % constants.RAPI_CERT_FILE)),
+              " variant 2 on master.example.com)" % pathutils.RAPI_CERT_FILE)),
       (None, ("File %s is missing from node(s) node2.example.com" %
-              constants.CLUSTER_DOMAIN_SECRET_FILE)),
+              pathutils.CLUSTER_DOMAIN_SECRET_FILE)),
       (None, ("File %s should not exist on node(s) node4.example.com" %
-              constants.CLUSTER_CONF_FILE)),
+              pathutils.CLUSTER_CONF_FILE)),
       (None, ("File %s is missing from node(s) node4.example.com" %
               hv_xen.XEND_CONFIG_FILE)),
       (None, ("File %s is missing from node(s) node3.example.com" %
-              constants.CLUSTER_CONF_FILE)),
+              pathutils.CLUSTER_CONF_FILE)),
       (None, ("File %s found with 2 different checksums (variant 1 on"
               " master.example.com; variant 2 on node4.example.com)" %
-              constants.CLUSTER_CONF_FILE)),
+              pathutils.CLUSTER_CONF_FILE)),
       (None, ("File %s is optional, but it must exist on all or no nodes (not"
               " found on master.example.com, node2.example.com,"
-              " node3.example.com)" % constants.RAPI_USERS_FILE)),
+              " node3.example.com)" % pathutils.RAPI_USERS_FILE)),
       (None, ("File %s is optional, but it must exist on all or no nodes (not"
               " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
       ("nodata.example.com", "Node did not return file checksum data"),
@@ -382,11 +396,13 @@ class TestClusterVerifyFiles(unittest.TestCase):
 
 
 class _FakeLU:
-  def __init__(self, cfg=NotImplemented, proc=NotImplemented):
+  def __init__(self, cfg=NotImplemented, proc=NotImplemented,
+               rpc=NotImplemented):
     self.warning_log = []
     self.info_log = []
     self.cfg = cfg
     self.proc = proc
+    self.rpc = rpc
 
   def LogWarning(self, text, *args):
     self.warning_log.append((text, args))
@@ -408,7 +424,7 @@ class TestLoadNodeEvacResult(unittest.TestCase):
             ]
 
           alloc_result = (moved, [], jobs)
-          assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
+          assert iallocator._NEVAC_RESULT(alloc_result)
 
           lu = _FakeLU()
           result = cmdlib._LoadNodeEvacResult(lu, alloc_result,
@@ -437,7 +453,7 @@ class TestLoadNodeEvacResult(unittest.TestCase):
     alloc_result = ([], [
       ("inst5191.example.com", "errormsg21178"),
       ], [])
-    assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
+    assert iallocator._NEVAC_RESULT(alloc_result)
 
     lu = _FakeLU()
     self.assertRaises(errors.OpExecError, cmdlib._LoadNodeEvacResult,
@@ -585,24 +601,24 @@ class TestComputeMinMaxSpec(unittest.TestCase):
       }
 
   def testNoneValue(self):
-    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE,
+    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
                                               self.ipolicy, None) is None)
 
   def testAutoValue(self):
-    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE,
+    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
                                               self.ipolicy,
                                               constants.VALUE_AUTO) is None)
 
   def testNotDefined(self):
-    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT,
+    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
                                               self.ipolicy, 3) is None)
 
   def testNoMinDefined(self):
-    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE,
+    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
                                               self.ipolicy, 128) is None)
 
   def testNoMaxDefined(self):
-    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
+    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT, None,
                                                 self.ipolicy, 16) is None)
 
   def testOutOfRange(self):
@@ -612,9 +628,14 @@ class TestComputeMinMaxSpec(unittest.TestCase):
                         (constants.ISPEC_DISK_COUNT, 0)):
       min_v = self.ipolicy[constants.ISPECS_MIN].get(name, val)
       max_v = self.ipolicy[constants.ISPECS_MAX].get(name, val)
-      self.assertEqual(cmdlib._ComputeMinMaxSpec(name, self.ipolicy, val),
+      self.assertEqual(cmdlib._ComputeMinMaxSpec(name, None,
+                                                 self.ipolicy, val),
                        "%s value %s is not in range [%s, %s]" %
                        (name, val,min_v, max_v))
+      self.assertEqual(cmdlib._ComputeMinMaxSpec(name, "1",
+                                                 self.ipolicy, val),
+                       "%s/1 value %s is not in range [%s, %s]" %
+                       (name, val,min_v, max_v))
 
   def test(self):
     for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
@@ -624,7 +645,7 @@ class TestComputeMinMaxSpec(unittest.TestCase):
                         (constants.ISPEC_DISK_SIZE, 0),
                         (constants.ISPEC_DISK_COUNT, 1),
                         (constants.ISPEC_DISK_COUNT, 5)):
-      self.assertTrue(cmdlib._ComputeMinMaxSpec(name, self.ipolicy, val)
+      self.assertTrue(cmdlib._ComputeMinMaxSpec(name, None, self.ipolicy, val)
                       is None)
 
 
@@ -974,6 +995,9 @@ class _FakeConfigForGenDiskTemplate:
   def GenerateDRBDSecret(self, ec_id):
     return "ec%s-secret%s" % (ec_id, self._secret.next())
 
+  def GetInstanceInfo(self, _):
+    return "foobar"
+
 
 class _FakeProcForGenDiskTemplate:
   def GetECId(self):
@@ -991,6 +1015,10 @@ class TestGenerateDiskTemplate(unittest.TestCase):
     self.lu = _FakeLU(cfg=cfg, proc=proc)
     self.nodegroup = nodegroup
 
+  @staticmethod
+  def GetDiskParams():
+    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
+
   def testWrongDiskTemplate(self):
     gdt = cmdlib._GenerateDiskTemplate
     disk_template = "##unknown##"
@@ -1000,7 +1028,7 @@ class TestGenerateDiskTemplate(unittest.TestCase):
     self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
                       "inst26831.example.com", "node30113.example.com", [], [],
                       NotImplemented, NotImplemented, 0, self.lu.LogInfo,
-                      self.nodegroup.diskparams)
+                      self.GetDiskParams())
 
   def testDiskless(self):
     gdt = cmdlib._GenerateDiskTemplate
@@ -1008,7 +1036,7 @@ class TestGenerateDiskTemplate(unittest.TestCase):
     result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
                  "node30113.example.com", [], [],
                  NotImplemented, NotImplemented, 0, self.lu.LogInfo,
-                 self.nodegroup.diskparams)
+                 self.GetDiskParams())
     self.assertEqual(result, [])
 
   def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
@@ -1027,14 +1055,14 @@ class TestGenerateDiskTemplate(unittest.TestCase):
                       template, "inst25088.example.com",
                       "node185.example.com", ["node323.example.com"], [],
                       NotImplemented, NotImplemented, base_index,
-                      self.lu.LogInfo, self.nodegroup.diskparams,
+                      self.lu.LogInfo, self.GetDiskParams(),
                       _req_file_storage=req_file_storage,
                       _req_shr_file_storage=req_shr_file_storage)
 
     result = gdt(self.lu, template, "inst21662.example.com",
                  "node21741.example.com", [],
                  disk_info, file_storage_dir, file_driver, base_index,
-                 self.lu.LogInfo, self.nodegroup.diskparams,
+                 self.lu.LogInfo, self.GetDiskParams(),
                  _req_file_storage=req_file_storage,
                  _req_shr_file_storage=req_shr_file_storage)
 
@@ -1184,12 +1212,12 @@ class TestGenerateDiskTemplate(unittest.TestCase):
     self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
                       "inst827.example.com", "node1334.example.com", [],
                       disk_info, NotImplemented, NotImplemented, 0,
-                      self.lu.LogInfo, self.nodegroup.diskparams)
+                      self.lu.LogInfo, self.GetDiskParams())
 
     result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
                  "node1334.example.com", ["node12272.example.com"],
                  disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
-                 self.nodegroup.diskparams)
+                 self.GetDiskParams())
 
     for (idx, disk) in enumerate(result):
       self.assertTrue(isinstance(disk, objects.Disk))
@@ -1207,7 +1235,7 @@ class TestGenerateDiskTemplate(unittest.TestCase):
 
       self.assertEqual(len(disk.children), 2)
       self.assertEqual(disk.children[0].size, disk.size)
-      self.assertEqual(disk.children[1].size, cmdlib.DRBD_META_SIZE)
+      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
 
     self._CheckIvNames(result, 0, len(disk_info))
     cmdlib._UpdateIvNames(0, result)
@@ -1223,5 +1251,255 @@ class TestGenerateDiskTemplate(unittest.TestCase):
       ])
 
 
+class _ConfigForDiskWipe:
+  def __init__(self, exp_node):
+    self._exp_node = exp_node
+
+  def SetDiskID(self, device, node):
+    assert isinstance(device, objects.Disk)
+    assert node == self._exp_node
+
+
+class _RpcForDiskWipe:
+  def __init__(self, exp_node, pause_cb, wipe_cb):
+    self._exp_node = exp_node
+    self._pause_cb = pause_cb
+    self._wipe_cb = wipe_cb
+
+  def call_blockdev_pause_resume_sync(self, node, disks, pause):
+    assert node == self._exp_node
+    return rpc.RpcResult(data=self._pause_cb(disks, pause))
+
+  def call_blockdev_wipe(self, node, bdev, offset, size):
+    assert node == self._exp_node
+    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
+
+
+class _DiskPauseTracker:
+  def __init__(self):
+    self.history = []
+
+  def __call__(self, (disks, instance), pause):
+    assert not (set(disks) - set(instance.disks))
+
+    self.history.extend((i.logical_id, i.size, pause)
+                        for i in disks)
+
+    return (True, [True] * len(disks))
+
+
+class _DiskWipeProgressTracker:
+  def __init__(self, start_offset):
+    self._start_offset = start_offset
+    self.progress = {}
+
+  def __call__(self, (disk, _), offset, size):
+    assert isinstance(offset, (long, int))
+    assert isinstance(size, (long, int))
+
+    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
+
+    assert offset >= self._start_offset
+    assert (offset + size) <= disk.size
+
+    assert size > 0
+    assert size <= constants.MAX_WIPE_CHUNK
+    assert size <= max_chunk_size
+
+    assert offset == self._start_offset or disk.logical_id in self.progress
+
+    # Keep track of progress
+    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
+
+    assert cur_progress == offset
+
+    # Record progress
+    self.progress[disk.logical_id] += size
+
+    return (True, None)
+
+
+class TestWipeDisks(unittest.TestCase):
+  def _FailingPauseCb(self, (disks, _), pause):
+    self.assertEqual(len(disks), 3)
+    self.assertTrue(pause)
+    # Simulate an RPC error
+    return (False, "error")
+
+  def testPauseFailure(self):
+    node_name = "node1372.example.com"
+
+    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
+                                     NotImplemented),
+                 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),
+      ]
+
+    instance = objects.Instance(name="inst21201",
+                                primary_node=node_name,
+                                disk_template=constants.DT_PLAIN,
+                                disks=disks)
+
+    self.assertRaises(errors.OpExecError, cmdlib._WipeDisks, lu, instance)
+
+  def _FailingWipeCb(self, (disk, _), offset, size):
+    # This should only ever be called for the first disk
+    self.assertEqual(disk.logical_id, "disk0")
+    return (False, None)
+
+  def testFailingWipe(self):
+    node_name = "node13445.example.com"
+    pt = _DiskPauseTracker()
+
+    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pt, self._FailingWipeCb),
+                 cfg=_ConfigForDiskWipe(node_name))
+
+    disks = [
+      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
+                   size=100 * 1024),
+      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
+                   size=500 * 1024),
+      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
+      ]
+
+    instance = objects.Instance(name="inst562",
+                                primary_node=node_name,
+                                disk_template=constants.DT_PLAIN,
+                                disks=disks)
+
+    try:
+      cmdlib._WipeDisks(lu, instance)
+    except errors.OpExecError, err:
+      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
+    else:
+      self.fail("Did not raise exception")
+
+    # Check if all disks were paused and resumed
+    self.assertEqual(pt.history, [
+      ("disk0", 100 * 1024, True),
+      ("disk1", 500 * 1024, True),
+      ("disk2", 256, True),
+      ("disk0", 100 * 1024, False),
+      ("disk1", 500 * 1024, False),
+      ("disk2", 256, False),
+      ])
+
+  def _PrepareWipeTest(self, start_offset, disks):
+    node_name = "node-with-offset%s.example.com" % start_offset
+    pauset = _DiskPauseTracker()
+    progresst = _DiskWipeProgressTracker(start_offset)
+
+    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
+                 cfg=_ConfigForDiskWipe(node_name))
+
+    instance = objects.Instance(name="inst3560",
+                                primary_node=node_name,
+                                disk_template=constants.DT_PLAIN,
+                                disks=disks)
+
+    return (lu, instance, pauset, progresst)
+
+  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",
+                   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",
+                   size=constants.MAX_WIPE_CHUNK),
+      ]
+
+    (lu, instance, pauset, progresst) = self._PrepareWipeTest(0, disks)
+
+    cmdlib._WipeDisks(lu, instance)
+
+    self.assertEqual(pauset.history, [
+      ("disk0", 1024, True),
+      ("disk1", 500 * 1024, True),
+      ("disk2", 128, True),
+      ("disk3", constants.MAX_WIPE_CHUNK, True),
+      ("disk0", 1024, False),
+      ("disk1", 500 * 1024, False),
+      ("disk2", 128, False),
+      ("disk3", constants.MAX_WIPE_CHUNK, False),
+      ])
+
+    # Ensure the complete disk has been wiped
+    self.assertEqual(progresst.progress,
+                     dict((i.logical_id, i.size) for i in disks))
+
+  def testWipeWithStartOffset(self):
+    for start_offset in [0, 280, 8895, 1563204]:
+      disks = [
+        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
+                     size=128),
+        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
+                     size=start_offset + (100 * 1024)),
+        ]
+
+      (lu, instance, pauset, progresst) = \
+        self._PrepareWipeTest(start_offset, disks)
+
+      # Test start offset with only one disk
+      cmdlib._WipeDisks(lu, instance,
+                        disks=[(1, disks[1], start_offset)])
+
+      # Only the second disk may have been paused and wiped
+      self.assertEqual(pauset.history, [
+        ("disk1", start_offset + (100 * 1024), True),
+        ("disk1", start_offset + (100 * 1024), False),
+        ])
+      self.assertEqual(progresst.progress, {
+        "disk1": disks[1].size,
+        })
+
+
+class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
+  def testLessThanOneMebibyte(self):
+    for i in [1, 2, 7, 512, 1000, 1023]:
+      lu = _FakeLU()
+      result = cmdlib._DiskSizeInBytesToMebibytes(lu, i)
+      self.assertEqual(result, 1)
+      self.assertEqual(len(lu.warning_log), 1)
+      self.assertEqual(len(lu.warning_log[0]), 2)
+      (_, (warnsize, )) = lu.warning_log[0]
+      self.assertEqual(warnsize, (1024 * 1024) - i)
+
+  def testEven(self):
+    for i in [1, 2, 7, 512, 1000, 1023]:
+      lu = _FakeLU()
+      result = cmdlib._DiskSizeInBytesToMebibytes(lu, i * 1024 * 1024)
+      self.assertEqual(result, i)
+      self.assertFalse(lu.warning_log)
+
+  def testLargeNumber(self):
+    for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
+      for j in [1, 2, 486, 326, 986, 1023]:
+        lu = _FakeLU()
+        size = (1024 * 1024 * i) + j
+        result = cmdlib._DiskSizeInBytesToMebibytes(lu, size)
+        self.assertEqual(result, i + 1, msg="Amount was not rounded up")
+        self.assertEqual(len(lu.warning_log), 1)
+        self.assertEqual(len(lu.warning_log[0]), 2)
+        (_, (warnsize, )) = lu.warning_log[0]
+        self.assertEqual(warnsize, (1024 * 1024) - j)
+
+
+class TestCopyLockList(unittest.TestCase):
+  def test(self):
+    self.assertEqual(cmdlib._CopyLockList([]), [])
+    self.assertEqual(cmdlib._CopyLockList(None), None)
+    self.assertEqual(cmdlib._CopyLockList(locking.ALL_SET), locking.ALL_SET)
+
+    names = ["foo", "bar"]
+    output = cmdlib._CopyLockList(names)
+    self.assertEqual(names, output)
+    self.assertNotEqual(id(names), id(output), msg="List was not copied")
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()